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

org.jetbrains.kotlin.gradle.plugin.KotlinPlugin.kt Maven / Gradle / Ivy

There is a newer version: 2.1.20-Beta1
Show newest version
package org.jetbrains.kotlin.gradle.plugin

import com.android.build.gradle.BaseExtension
import com.android.build.gradle.BasePlugin
import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.internal.variant.BaseVariantData
import com.android.build.gradle.internal.variant.BaseVariantOutputData
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.initialization.dsl.ScriptHandler
import org.gradle.api.internal.HasConvention
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.internal.AnnotationProcessingManager
import org.jetbrains.kotlin.gradle.internal.KotlinSourceSet
import org.jetbrains.kotlin.gradle.internal.KotlinSourceSetImpl
import org.jetbrains.kotlin.gradle.internal.initKapt
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import java.io.File
import java.net.URL
import java.util.*
import java.util.jar.Manifest
import javax.inject.Inject

val KOTLIN_AFTER_JAVA_TASK_SUFFIX = "AfterJava"

abstract class KotlinSourceSetProcessor(
        val project: ProjectInternal,
        val javaBasePlugin: JavaBasePlugin,
        val sourceSet: SourceSet,
        val pluginName: String,
        val compileTaskNameSuffix: String,
        val taskDescription: String,
        val compilerClass: Class
) {
    abstract protected fun doTargetSpecificProcessing()
    val logger = Logging.getLogger(this.javaClass)

    protected val sourceSetName: String = sourceSet.name
    protected val sourceRootDir: String = "src/${sourceSetName}/kotlin"
    protected val absoluteSourceRootDir: String = project.projectDir.path + "/" + sourceRootDir
    protected val kotlinSourceSet: KotlinSourceSet? by lazy { createKotlinSourceSet() }
    protected val kotlinDirSet: SourceDirectorySet? by lazy { createKotlinDirSet() }
    protected val kotlinTask: T by lazy { createKotlinCompileTask() }
    protected val kotlinTaskName: String by lazy { kotlinTask.name }

    public fun run() {
        if (kotlinSourceSet == null || kotlinDirSet == null) {
            return
        }
        addSourcesToKotlinDirSet()
        commonTaskConfiguration()
        doTargetSpecificProcessing()
    }

    open protected fun createKotlinSourceSet(): KotlinSourceSet? =
            if (sourceSet is HasConvention) {
                logger.kotlinDebug("Creating KotlinSourceSet for source set ${sourceSet}")
                val kotlinSourceSet = KotlinSourceSetImpl(sourceSet.name, project.fileResolver)
                sourceSet.convention.plugins.put(pluginName, kotlinSourceSet)
                kotlinSourceSet
            } else {
                null
            }

    open protected fun createKotlinDirSet(): SourceDirectorySet? {
        val srcDir = project.file(sourceRootDir)
        logger.kotlinDebug("Creating Kotlin SourceDirectorySet for source set $kotlinSourceSet with src dir $srcDir")
        val kotlinDirSet = kotlinSourceSet?.getKotlin()
        kotlinDirSet?.srcDir(srcDir)
        return kotlinDirSet
    }

    open protected fun addSourcesToKotlinDirSet() {
        logger.kotlinDebug("Adding Kotlin SourceDirectorySet $kotlinDirSet to source set $sourceSet")
        sourceSet.getAllJava()?.source(kotlinDirSet)
        sourceSet.getAllSource()?.source(kotlinDirSet)
        sourceSet.resources?.filter?.exclude { kotlinDirSet!!.contains(it.file) }
    }

    open protected fun createKotlinCompileTask(suffix: String = ""): T {
        val name = sourceSet.getCompileTaskName(compileTaskNameSuffix) + suffix
        logger.kotlinDebug("Creating kotlin compile task $name with class $compilerClass")
        val compile = project.tasks.create(name, compilerClass)
        compile.extensions.extraProperties.set("defaultModuleName", "${project.name}-$name")
        return compile
    }

    open protected fun commonTaskConfiguration() {
        javaBasePlugin.configureForSourceSet(sourceSet, kotlinTask)
        kotlinTask.description = taskDescription
        kotlinTask.source(kotlinDirSet)
        mapKotlinTaskProperties(project, kotlinTask)
    }
}

class Kotlin2JvmSourceSetProcessor(
        project: ProjectInternal,
        javaBasePlugin: JavaBasePlugin,
        sourceSet: SourceSet,
        val scriptHandler: ScriptHandler,
        val tasksProvider: KotlinTasksProvider
) : KotlinSourceSetProcessor(
        project, javaBasePlugin, sourceSet,
        pluginName = "kotlin",
        compileTaskNameSuffix = "kotlin",
        taskDescription = "Compiles the $sourceSet.kotlin.",
        compilerClass = tasksProvider.kotlinJVMCompileTaskClass
) {

    private companion object {
        private var cachedKotlinAnnotationProcessingDep: String? = null
    }

    override fun doTargetSpecificProcessing() {
        val kotlinAnnotationProcessingDep = cachedKotlinAnnotationProcessingDep ?: run {
            val projectVersion = loadKotlinVersionFromResource(project.logger)
            val dep = "org.jetbrains.kotlin:kotlin-annotation-processing:$projectVersion"
            cachedKotlinAnnotationProcessingDep = dep
            dep
        }

        val aptConfiguration = project.createAptConfiguration(sourceSet.name, kotlinAnnotationProcessingDep)

        project.afterEvaluate { project ->
            if (project != null) {
                kotlinTask.kotlinDestinationDir = File(project.buildDir, "kotlin-classes/$sourceSetName")

                for (dir in sourceSet.getJava().srcDirs) {
                    kotlinDirSet?.srcDir(dir)
                }

                val subpluginEnvironment = loadSubplugins(project)
                subpluginEnvironment.addSubpluginArguments(project, kotlinTask)

                val javaTask = project.tasks.findByName(sourceSet.compileJavaTaskName)
                if (javaTask !is JavaCompile) return@afterEvaluate

                var kotlinAfterJavaTask: AbstractCompile? = null

                if (aptConfiguration.dependencies.size > 1) {
                    javaTask.dependsOn(aptConfiguration.buildDependencies)

                    val (aptOutputDir, aptWorkingDir) = project.getAptDirsForSourceSet(sourceSetName)

                    val kaptManager = AnnotationProcessingManager(kotlinTask, javaTask, sourceSetName,
                            aptConfiguration.resolve(), aptOutputDir, aptWorkingDir, tasksProvider.tasksLoader)

                    kotlinAfterJavaTask = project.initKapt(kotlinTask, javaTask, kaptManager,
                            sourceSetName, null, subpluginEnvironment) {
                        createKotlinCompileTask(it)
                    }

                    if (kotlinAfterJavaTask != null) {
                        javaTask.doFirst {
                            kotlinAfterJavaTask!!.classpath = project.files(kotlinTask.classpath, javaTask.destinationDir)
                        }
                    }
                }

                configureJavaTask(kotlinTask, javaTask, kotlinAfterJavaTask, logger)
            }
        }
    }
}

class Kotlin2JsSourceSetProcessor(
        project: ProjectInternal,
        javaBasePlugin: JavaBasePlugin,
        sourceSet: SourceSet,
        val scriptHandler: ScriptHandler,
        val tasksProvider: KotlinTasksProvider
) : KotlinSourceSetProcessor(
        project, javaBasePlugin, sourceSet,
        pluginName = "kotlin2js",
        taskDescription = "Compiles the kotlin sources in $sourceSet to JavaScript.",
        compileTaskNameSuffix = "kotlin2Js",
        compilerClass = tasksProvider.kotlinJSCompileTaskClass
) {

    val copyKotlinJsTaskName = sourceSet.getTaskName("copy", "kotlinJs")
    val clean = project.tasks.findByName("clean")
    val build = project.tasks.findByName("build")

    val defaultKotlinDestinationDir = File(project.buildDir, "kotlin2js/${sourceSetName}")
    private fun kotlinTaskDestinationDir(): File? = kotlinTask.kotlinDestinationDir
    private fun kotlinJsDestinationDir(): File? = (kotlinTask.property("outputFile") as String).let { File(it) }.let { if (it.isDirectory) it else it.parentFile }

    private fun kotlinSourcePathsForSourceMap() = sourceSet.getAllSource()
            .map { it.path }
            .filter { it.endsWith(".kt") }
            .map { it.replace(absoluteSourceRootDir, (kotlinTask.property("sourceMapDestinationDir") as File).path) }

    private fun shouldGenerateSourceMap() = kotlinTask.property("sourceMap")

    override fun doTargetSpecificProcessing() {
        kotlinTask.kotlinDestinationDir = defaultKotlinDestinationDir
        build?.dependsOn(kotlinTaskName)
        clean?.dependsOn("clean" + kotlinTaskName.capitalize())

        createCleanSourceMapTask()
    }

    private fun createCleanSourceMapTask() {
        val taskName = sourceSet.getTaskName("clean", "sourceMap")
        val task = project.tasks.create(taskName, Delete::class.java)
        task.onlyIf { kotlinTask.property("sourceMap") as Boolean }
        task.delete(object : Closure(this) {
            override fun call(): String? = (kotlinTask.property("outputFile") as String) + ".map"
        })
        clean?.dependsOn(taskName)
    }
}


abstract class AbstractKotlinPlugin @Inject constructor(val scriptHandler: ScriptHandler, val tasksProvider: KotlinTasksProvider) : Plugin {
    abstract fun buildSourceSetProcessor(project: ProjectInternal, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet): KotlinSourceSetProcessor<*>

    public override fun apply(project: Project) {
        val javaBasePlugin = project.plugins.apply(JavaBasePlugin::class.java)
        val javaPluginConvention = project.convention.getPlugin(JavaPluginConvention::class.java)

        project.plugins.apply(JavaPlugin::class.java)

        configureSourceSetDefaults(project as ProjectInternal, javaBasePlugin, javaPluginConvention)
    }

    open protected fun configureSourceSetDefaults(project: ProjectInternal,
                                                  javaBasePlugin: JavaBasePlugin,
                                                  javaPluginConvention: JavaPluginConvention) {
        javaPluginConvention.sourceSets?.all(Action { sourceSet ->
            if (sourceSet != null) {
                buildSourceSetProcessor(project, javaBasePlugin, sourceSet).run()
            }
        })
    }
}


open class KotlinPlugin @Inject constructor(scriptHandler: ScriptHandler, tasksProvider: KotlinTasksProvider) : AbstractKotlinPlugin(scriptHandler, tasksProvider) {
    override fun buildSourceSetProcessor(project: ProjectInternal, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet) =
            Kotlin2JvmSourceSetProcessor(project, javaBasePlugin, sourceSet, scriptHandler, tasksProvider)

    override fun apply(project: Project) {
        project.createKaptExtension()
        super.apply(project)
    }
}


open class Kotlin2JsPlugin @Inject constructor(scriptHandler: ScriptHandler, tasksProvider: KotlinTasksProvider) : AbstractKotlinPlugin(scriptHandler, tasksProvider) {
    override fun buildSourceSetProcessor(project: ProjectInternal, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet) =
            Kotlin2JsSourceSetProcessor(project, javaBasePlugin, sourceSet, scriptHandler, tasksProvider)
}


open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHandler, val tasksProvider: KotlinTasksProvider) : Plugin {

    val log = Logging.getLogger(this.javaClass)

    public override fun apply(p0: Project) {

        val project = p0 as ProjectInternal
        val ext = project.extensions.getByName("android") as BaseExtension

        val version = loadAndroidPluginVersion()
        if (version != null) {
            val minimalVersion = "1.1.0"
            if (compareVersionNumbers(version, minimalVersion) < 0) {
                throw IllegalStateException("Kotlin: Unsupported version of com.android.tools.build:gradle plugin: version $minimalVersion or higher should be used with kotlin-android plugin")
            }
        }

        val aptConfigurations = hashMapOf()

        val projectVersion = loadKotlinVersionFromResource(log)
        val kotlinAnnotationProcessingDep = "org.jetbrains.kotlin:kotlin-annotation-processing:$projectVersion"

        ext.sourceSets.all(Action { sourceSet ->
            if (sourceSet is HasConvention) {
                val sourceSetName = sourceSet.name
                val kotlinSourceSet = KotlinSourceSetImpl(sourceSetName, project.fileResolver)
                sourceSet.convention.plugins.put("kotlin", kotlinSourceSet)
                val kotlinDirSet = kotlinSourceSet.getKotlin()
                kotlinDirSet.srcDir(project.file("src/$sourceSetName/kotlin"))

                aptConfigurations.put(sourceSet.name,
                        project.createAptConfiguration(sourceSet.name, kotlinAnnotationProcessingDep))

                /*TODO: before 0.11 gradle android plugin there was:
                  sourceSet.getAllJava().source(kotlinDirSet)
                  sourceSet.getAllSource().source(kotlinDirSet)
                  AndroidGradleWrapper.getResourceFilter(sourceSet)?.exclude(KSpec({ elem ->
                    kotlinDirSet.contains(elem.getFile())
                  }))
                 but those methods were removed so commented as temporary hack*/

                project.logger.kotlinDebug("Created kotlin sourceDirectorySet at ${kotlinDirSet.srcDirs}")
            }
        })

        val extensions = (ext as ExtensionAware).extensions
        extensions.add("kotlinOptions", tasksProvider.kotlinJVMOptionsClass)
        AndroidGradleWrapper.setNoJdk(extensions.getByName("kotlinOptions"))

        project.createKaptExtension()

        project.afterEvaluate { project ->
            if (project != null) {
                val plugin = (project.plugins.findPlugin("android")
                                ?: project.plugins.findPlugin("android-library")) as BasePlugin

                val variantManager = AndroidGradleWrapper.getVariantDataManager(plugin)
                processVariantData(variantManager.variantDataList, project,
                        ext, plugin, aptConfigurations)
            }
        }
    }

    private fun processVariantData(
            variantDataList: List>,
            project: Project,
            androidExt: BaseExtension,
            androidPlugin: BasePlugin,
            aptConfigurations: Map
    ) {
        val logger = project.logger
        val kotlinOptions = getExtension(androidExt, "kotlinOptions")

        val subpluginEnvironment = loadSubplugins(project)

        for (variantData in variantDataList) {
            val variantDataName = variantData.name
            logger.kotlinDebug("Process variant [$variantDataName]")

            val javaTask = AndroidGradleWrapper.getJavaCompile(variantData)
            if (javaTask == null) {
                logger.info("KOTLIN: javaTask is missing for $variantDataName, so Kotlin files won't be compiled for it")
                continue
            }

            val kotlinTaskName = "compile${variantDataName.capitalize()}Kotlin"
            val kotlinTask = tasksProvider.createKotlinJVMTask(project, kotlinTaskName)
            mapKotlinTaskProperties(project, kotlinTask)

            kotlinTask.extensions.extraProperties.set("defaultModuleName", "${project.name}-$kotlinTaskName")
            if (kotlinOptions != null) {
                kotlinTask.setProperty("kotlinOptions", kotlinOptions)
            }

            // store kotlin classes in separate directory. They will serve as class-path to java compiler
            kotlinTask.kotlinDestinationDir = File(project.buildDir, "tmp/kotlin-classes/$variantDataName")
            kotlinTask.destinationDir = javaTask.destinationDir
            kotlinTask.description = "Compiles the ${variantDataName} kotlin."
            kotlinTask.setDependsOn(javaTask.dependsOn)

            fun SourceDirectorySet.addSourceDirectories(additionalSourceFiles: Collection) {
                for (dir in additionalSourceFiles) {
                    this.srcDir(dir)
                    logger.kotlinDebug("Source directory ${dir.absolutePath} was added to kotlin source for $kotlinTaskName")
                }
            }

            val aptFiles = arrayListOf()

            // getSortedSourceProviders should return only actual java sources, generated sources should be collected earlier
            val providers = variantData.variantConfiguration.sortedSourceProviders
            for (provider in providers) {
                val javaSrcDirs = AndroidGradleWrapper.getJavaSrcDirs(provider as AndroidSourceSet)
                val kotlinSourceSet = getExtension(provider, "kotlin")
                val kotlinSourceDirectorySet = kotlinSourceSet.getKotlin()
                kotlinTask.source(kotlinSourceDirectorySet)

                kotlinSourceDirectorySet.addSourceDirectories(javaSrcDirs)

                val aptConfiguration = aptConfigurations[(provider as AndroidSourceSet).name]
                // Ignore if there's only an annotation processor wrapper in dependencies (added by default)
                if (aptConfiguration != null && aptConfiguration.dependencies.size > 1) {
                    javaTask.dependsOn(aptConfiguration.buildDependencies)
                    aptFiles.addAll(aptConfiguration.resolve())
                }
            }

            // getJavaSources should return the Java sources used for compilation
            // We want to collect only generated files, like R-class output dir
            // Actual java sources will be collected later
            val additionalSourceFiles = AndroidGradleWrapper.getGeneratedSourceDirs(variantData)
            for (file in additionalSourceFiles) {
                kotlinTask.source(file)
                logger.kotlinDebug("Source directory with generated files ${file.absolutePath} was added to kotlin source for $kotlinTaskName")
            }

            subpluginEnvironment.addSubpluginArguments(project, kotlinTask)

            // should not be evaluated until right before compileKotlin evaluation since android can change
            // java classpath during project evaluation (see prepareComAndroidSupportSupportV42311Library)
            val fullClasspath = lazy {
                val androidRT = project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt))
                (javaTask.classpath + androidRT) - project.files(kotlinTask.kotlinDestinationDir)
            }
            kotlinTask.classpath = javaTask.classpath
            kotlinTask.updateClasspathBeforeTask { fullClasspath.value }
            kotlinTask.doFirst {
                for (task in project.getTasksByName(kotlinTaskName + KOTLIN_AFTER_JAVA_TASK_SUFFIX, false)) {
                    (task as AbstractCompile).classpath = project.files(fullClasspath.value, javaTask.destinationDir)
                }
            }

            val (aptOutputDir, aptWorkingDir) = project.getAptDirsForSourceSet(variantDataName)
            variantData.addJavaSourceFoldersToModel(aptOutputDir)
            var kotlinAfterJavaTask: AbstractCompile? = null

            if (javaTask is JavaCompile && aptFiles.isNotEmpty()) {
                val kaptManager = AnnotationProcessingManager(kotlinTask, javaTask, variantDataName,
                        aptFiles.toSet(), aptOutputDir, aptWorkingDir, tasksProvider.tasksLoader, variantData)

                kotlinTask.storeKaptAnnotationsFile(kaptManager)

                kotlinAfterJavaTask = project.initKapt(kotlinTask, javaTask, kaptManager, variantDataName, kotlinOptions, subpluginEnvironment) {
                    tasksProvider.createKotlinJVMTask(project, kotlinTaskName + KOTLIN_AFTER_JAVA_TASK_SUFFIX)
                }
            }

            configureJavaTask(kotlinTask, javaTask, kotlinAfterJavaTask, logger)
        }
    }

    fun  getExtension(obj: Any, extensionName: String): T {
        if (obj is ExtensionAware) {
            val result = obj.extensions.findByName(extensionName)
            if (result != null) {
                return result as T
            }
        }
        val result = (obj as HasConvention).convention.plugins[extensionName]
        return result as T
    }
}

private fun configureJavaTask(kotlinTask: AbstractCompile, javaTask: AbstractCompile, kotlinAfterJavaTask: AbstractCompile?, logger: Logger) {
    // Since we cannot update classpath statically, java not able to detect changes in the classpath after kotlin compiler.
    // Therefore this (probably inefficient since java cannot decide "uptodateness" by the list of changed class files, but told
    // explicitly being out of date whenever any kotlin files are compiled
    if (kotlinTask.anyClassesCompiled != null) {
        kotlinTask.anyClassesCompiled = false

        javaTask.outputs.upToDateWhen { task ->
            val kotlinClassesCompiled = kotlinTask.anyClassesCompiled ?: false
            if (kotlinClassesCompiled) {
                logger.info("Marking $task out of date, because kotlin classes are changed")
            }
            !kotlinClassesCompiled
        }
    }

    javaTask.dependsOn(kotlinTask.name)
    /*
     * It's important to modify javaTask.classpath only in doFirst,
     * because Android plugin uses ConventionMapping to modify it too (see JavaCompileConfigAction.execute),
     * and setting classpath explicitly prevents usage of Android mappings.
     * Also classpath setted by Android can be modified after excecution of some tasks (see VarianConfiguration.getCompileClasspath)
     * ex. it adds some support libraries jars after execution of prepareComAndroidSupportSupportV42311Library task,
     * so it's only safe to modify javaTask.classpath right before its usage
     */
    if (kotlinAfterJavaTask == null) {
        javaTask.appendClasspathDynamically(kotlinTask.kotlinDestinationDir!!)
    }
}

private fun loadSubplugins(project: Project): SubpluginEnvironment {
    try {
        val subplugins = ServiceLoader.load(KotlinGradleSubplugin::class.java, project.buildscript.classLoader).toList()

        val classpath = project.buildscript.configurations.getByName("classpath")
        val resolvedClasspathArtifacts = classpath.resolvedConfiguration.resolvedArtifacts.toList()
        val subpluginClasspaths = hashMapOf>()

        for (subplugin in subplugins) {
            val file = resolvedClasspathArtifacts
                    .firstOrNull {
                        val id = it.moduleVersion.id
                        subplugin.getGroupName() == id.group && subplugin.getArtifactName() == id.name
                    }?.file
            if (file != null) {
                subpluginClasspaths.put(subplugin, listOf(file))
            }
        }

        return SubpluginEnvironment(subpluginClasspaths, subplugins)
    } catch (e: NoClassDefFoundError) {
        // Skip plugin loading if KotlinGradleSubplugin is not defined.
        // It is true now for tests in kotlin-gradle-plugin-core.
        return SubpluginEnvironment(mapOf(), listOf())
    }
}

class SubpluginEnvironment(
    val subpluginClasspaths: Map>,
    val subplugins: List
) {

    fun addSubpluginArguments(project: Project, compileTask: AbstractCompile) {
        val realPluginClasspaths = arrayListOf()
        val pluginArguments = arrayListOf()
        fun getPluginOptionString(pluginId: String, key: String, value: String) = "plugin:$pluginId:$key=$value"

        for (subplugin in subplugins) {
            if (!subplugin.isApplicable(project, compileTask)) continue

            with (subplugin) {
                project.logger.kotlinDebug("Subplugin ${getPluginName()} (${getGroupName()}:${getArtifactName()}) loaded.")
            }

            val subpluginClasspath = subpluginClasspaths[subplugin]
            if (subpluginClasspath != null) {
                subpluginClasspath.forEach { realPluginClasspaths.add(it.absolutePath) }

                for (arg in subplugin.getExtraArguments(project, compileTask)) {
                    val option = getPluginOptionString(subplugin.getPluginName(), arg.key, arg.value)
                    pluginArguments.add(option)
                }
            }
        }

        val extraProperties = compileTask.extensions.extraProperties
        extraProperties.set("compilerPluginClasspaths", realPluginClasspaths.toTypedArray())
        extraProperties.set("compilerPluginArguments", pluginArguments.toTypedArray())
    }
}

open class GradleUtils(val scriptHandler: ScriptHandler, val project: ProjectInternal) {
    public fun resolveDependencies(vararg coordinates: String): Collection {
        val dependencyHandler: DependencyHandler = scriptHandler.dependencies
        val configurationsContainer: ConfigurationContainer = scriptHandler.configurations

        val deps = coordinates.map { dependencyHandler.create(it) }
        val configuration = configurationsContainer.detachedConfiguration(*deps.toTypedArray())

        return configuration.resolvedConfiguration.getFiles { true }
    }

    public fun kotlinPluginVersion(): String = project.properties["kotlin.gradle.plugin.version"] as String
    public fun kotlinPluginArtifactCoordinates(artifact: String): String = "org.jetbrains.kotlin:${artifact}:${kotlinPluginVersion()}"
    public fun kotlinJsLibraryCoordinates(): String = kotlinPluginArtifactCoordinates("kotlin-js-library")

    public fun resolveJsLibrary(): File = resolveDependencies(kotlinJsLibraryCoordinates()).first()
}

internal operator fun FileCollection.plus(other: FileCollection) = this.plus(other)
internal operator fun FileCollection.minus(other: FileCollection) = this.minus(other)

fun AbstractCompile.storeKaptAnnotationsFile(kapt: AnnotationProcessingManager) {
    extensions.extraProperties.set("kaptAnnotationsFile", kapt.getAnnotationFile())
}

private fun Project.getAptDirsForSourceSet(sourceSetName: String): Pair {
    val aptOutputDir = File(buildDir, "generated/source/kapt")
    val aptOutputDirForVariant = File(aptOutputDir, sourceSetName)

    val aptWorkingDir = File(buildDir, "tmp/kapt")
    val aptWorkingDirForVariant = File(aptWorkingDir, sourceSetName)

    return aptOutputDirForVariant to aptWorkingDirForVariant
}

private fun Project.createAptConfiguration(sourceSetName: String, kotlinAnnotationProcessingDep: String): Configuration {
    val aptConfigurationName = if (sourceSetName != "main") "kapt${sourceSetName.capitalize()}" else "kapt"

    val aptConfiguration = configurations.create(aptConfigurationName)
    aptConfiguration.dependencies.add(dependencies.create(kotlinAnnotationProcessingDep))

    return aptConfiguration
}

private fun Project.createKaptExtension() {
    extensions.create("kapt", KaptExtension::class.java)
}

//copied from BasePlugin.getLocalVersion
private fun loadAndroidPluginVersion(): String? {
    try {
        val clazz = BasePlugin::class.java
        val className = clazz.simpleName + ".class"
        val classPath = clazz.getResource(className).toString()
        if (!classPath.startsWith("jar")) {
            // Class not from JAR, unlikely
            return null
        }
        val manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";

        val jarConnection = URL(manifestPath).openConnection()
        jarConnection.useCaches = false
        val jarInputStream = jarConnection.inputStream
        val attr = Manifest(jarInputStream).mainAttributes
        jarInputStream.close()
        return attr.getValue("Plugin-Version")
    } catch (t: Throwable) {
        return null;
    }
}

//Copied from StringUtil.compareVersionNumbers
private fun compareVersionNumbers(v1: String?, v2: String?): Int {
    if (v1 == null && v2 == null) {
        return 0
    }
    if (v1 == null) {
        return -1
    }
    if (v2 == null) {
        return 1
    }

    val pattern = "[\\.\\_\\-]".toRegex()
    val digitsPattern = "\\d+".toRegex()
    val part1 = v1.split(pattern)
    val part2 = v2.split(pattern)

    var idx = 0
    while (idx < part1.size && idx < part2.size) {
        val p1 = part1[idx]
        val p2 = part2[idx]

        val cmp: Int
        if (p1.matches(digitsPattern) && p2.matches(digitsPattern)) {
            cmp = p1.toInt().compareTo(p2.toInt())
        } else {
            cmp = part1[idx].compareTo(part2[idx])
        }
        if (cmp != 0) return cmp
        idx++
    }

    if (part1.size == part2.size) {
        return 0
    } else {
        val left = part1.size > idx
        val parts = if (left) part1 else part2

        while (idx < parts.size) {
            val p = parts[idx]
            val cmp: Int
            if (p.matches(digitsPattern)) {
                cmp = Integer(p).compareTo(0)
            } else {
                cmp = 1
            }
            if (cmp != 0) return if (left) cmp else -cmp
            idx++
        }
        return 0
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy