| /* |
| * Copyright 2010-2019 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 |
| |
| import org.gradle.api.Project |
| import org.gradle.api.Task |
| import org.gradle.api.artifacts.Dependency |
| import org.gradle.api.artifacts.ProjectDependency |
| import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder |
| import org.jetbrains.plugins.gradle.tooling.ModelBuilderService |
| import java.io.File |
| import java.io.Serializable |
| import java.lang.reflect.InvocationTargetException |
| import java.util.* |
| |
| interface ArgsInfo : Serializable { |
| val currentArguments: List<String> |
| val defaultArguments: List<String> |
| val dependencyClasspath: List<String> |
| } |
| |
| data class ArgsInfoImpl( |
| override val currentArguments: List<String>, |
| override val defaultArguments: List<String>, |
| override val dependencyClasspath: List<String> |
| ) : ArgsInfo { |
| |
| constructor(argsInfo: ArgsInfo) : this( |
| ArrayList(argsInfo.currentArguments), |
| ArrayList(argsInfo.defaultArguments), |
| ArrayList(argsInfo.dependencyClasspath) |
| ) |
| } |
| |
| typealias CompilerArgumentsBySourceSet = Map<String, ArgsInfo> |
| |
| /** |
| * Creates deep copy in order to avoid holding links to Proxy objects created by gradle tooling api |
| */ |
| fun CompilerArgumentsBySourceSet.deepCopy(): CompilerArgumentsBySourceSet { |
| val result = HashMap<String, ArgsInfo>() |
| this.forEach { key, value -> result[key] = ArgsInfoImpl(value) } |
| return result |
| } |
| |
| interface KotlinGradleModel : Serializable { |
| val hasKotlinPlugin: Boolean |
| val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet |
| val coroutines: String? |
| val platformPluginId: String? |
| val implements: List<String> |
| val kotlinTarget: String? |
| val kotlinTaskProperties: KotlinTaskPropertiesBySourceSet |
| val gradleUserHome: String |
| } |
| |
| data class KotlinGradleModelImpl( |
| override val hasKotlinPlugin: Boolean, |
| override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet, |
| override val coroutines: String?, |
| override val platformPluginId: String?, |
| override val implements: List<String>, |
| override val kotlinTarget: String? = null, |
| override val kotlinTaskProperties: KotlinTaskPropertiesBySourceSet, |
| override val gradleUserHome: String |
| ) : KotlinGradleModel |
| |
| abstract class AbstractKotlinGradleModelBuilder : ModelBuilderService { |
| companion object { |
| val kotlinCompileJvmTaskClasses = listOf( |
| "org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated", |
| "org.jetbrains.kotlin.gradle.tasks.KotlinCompileWithWorkers_Decorated" |
| ) |
| |
| val kotlinCompileTaskClasses = kotlinCompileJvmTaskClasses + listOf( |
| "org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile_Decorated", |
| "org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon_Decorated", |
| "org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompileWithWorkers_Decorated", |
| "org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommonWithWorkers_Decorated" |
| ) |
| val platformPluginIds = listOf("kotlin-platform-jvm", "kotlin-platform-js", "kotlin-platform-common") |
| val pluginToPlatform = linkedMapOf( |
| "kotlin" to "kotlin-platform-jvm", |
| "kotlin2js" to "kotlin-platform-js" |
| ) |
| val kotlinPluginIds = listOf("kotlin", "kotlin2js", "kotlin-android") |
| val ABSTRACT_KOTLIN_COMPILE_CLASS = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile" |
| |
| val kotlinProjectExtensionClass = "org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension" |
| val kotlinSourceSetClass = "org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet" |
| |
| val kotlinPluginWrapper = "org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapperKt" |
| |
| fun Task.getSourceSetName(): String = try { |
| javaClass.methods.firstOrNull { it.name.startsWith("getSourceSetName") && it.parameterTypes.isEmpty() }?.invoke(this) as? String |
| } catch (e: InvocationTargetException) { |
| null // can be thrown if property is not initialized yet |
| } ?: "main" |
| } |
| } |
| |
| class KotlinGradleModelBuilder : AbstractKotlinGradleModelBuilder() { |
| override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { |
| return ErrorMessageBuilder.create(project, e, "Gradle import errors") |
| .withDescription("Unable to build Kotlin project configuration") |
| } |
| |
| override fun canBuild(modelName: String?): Boolean = modelName == KotlinGradleModel::class.java.name |
| |
| private fun getImplementedProjects(project: Project): List<Project> { |
| return listOf("expectedBy", "implement") |
| .flatMap { project.configurations.findByName(it)?.dependencies ?: emptySet<Dependency>() } |
| .filterIsInstance<ProjectDependency>() |
| .mapNotNull { it.dependencyProject } |
| } |
| |
| // see GradleProjectResolverUtil.getModuleId() in IDEA codebase |
| private fun Project.pathOrName() = if (path == ":") name else path |
| |
| @Suppress("UNCHECKED_CAST") |
| private fun Task.getCompilerArguments(methodName: String): List<String>? { |
| return try { |
| javaClass.getDeclaredMethod(methodName).invoke(this) as List<String> |
| } catch (e: Exception) { |
| // No argument accessor method is available |
| null |
| } |
| } |
| |
| private fun Task.getDependencyClasspath(): List<String> { |
| try { |
| val abstractKotlinCompileClass = javaClass.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE_CLASS) |
| val getCompileClasspath = abstractKotlinCompileClass.getDeclaredMethod("getCompileClasspath").apply { isAccessible = true } |
| @Suppress("UNCHECKED_CAST") |
| return (getCompileClasspath.invoke(this) as Collection<File>).map { it.path } |
| } catch (e: ClassNotFoundException) { |
| // Leave arguments unchanged |
| } catch (e: NoSuchMethodException) { |
| // Leave arguments unchanged |
| } catch (e: InvocationTargetException) { |
| // We can safely ignore this exception here as getCompileClasspath() gets called again at a later time |
| // Leave arguments unchanged |
| } |
| return emptyList() |
| } |
| |
| private fun getCoroutines(project: Project): String? { |
| val kotlinExtension = project.extensions.findByName("kotlin") ?: return null |
| val experimentalExtension = try { |
| kotlinExtension::class.java.getMethod("getExperimental").invoke(kotlinExtension) |
| } catch (e: NoSuchMethodException) { |
| return null |
| } |
| |
| return try { |
| experimentalExtension::class.java.getMethod("getCoroutines").invoke(experimentalExtension)?.toString() |
| } catch (e: NoSuchMethodException) { |
| null |
| } |
| } |
| |
| override fun buildAll(modelName: String?, project: Project): KotlinGradleModelImpl { |
| val kotlinPluginId = kotlinPluginIds.singleOrNull { project.plugins.findPlugin(it) != null } |
| val platformPluginId = platformPluginIds.singleOrNull { project.plugins.findPlugin(it) != null } |
| |
| val compilerArgumentsBySourceSet = LinkedHashMap<String, ArgsInfo>() |
| val extraProperties = HashMap<String, KotlinTaskProperties>() |
| |
| project.getAllTasks(false)[project]?.forEach { compileTask -> |
| if (compileTask.javaClass.name !in kotlinCompileTaskClasses) return@forEach |
| |
| val sourceSetName = compileTask.getSourceSetName() |
| val currentArguments = compileTask.getCompilerArguments("getSerializedCompilerArguments") |
| ?: compileTask.getCompilerArguments("getSerializedCompilerArgumentsIgnoreClasspathIssues") ?: emptyList() |
| val defaultArguments = compileTask.getCompilerArguments("getDefaultSerializedCompilerArguments").orEmpty() |
| val dependencyClasspath = compileTask.getDependencyClasspath() |
| compilerArgumentsBySourceSet[sourceSetName] = ArgsInfoImpl(currentArguments, defaultArguments, dependencyClasspath) |
| extraProperties.acknowledgeTask(compileTask) |
| } |
| |
| val platform = platformPluginId ?: pluginToPlatform.entries.singleOrNull { project.plugins.findPlugin(it.key) != null }?.value |
| val implementedProjects = getImplementedProjects(project) |
| |
| return KotlinGradleModelImpl( |
| kotlinPluginId != null || platformPluginId != null, |
| compilerArgumentsBySourceSet, |
| getCoroutines(project), |
| platform, |
| implementedProjects.map { it.pathOrName() }, |
| platform ?: kotlinPluginId, |
| extraProperties, |
| project.gradle.gradleUserHomeDir.absolutePath |
| ) |
| } |
| } |