Implement ide-iml-to-gradle-generator
This module is used to generate build.gradle.kts files of Kotlin IDE plugin
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlinStdlib("jdk8"))
|
||||
implementation(intellijDep())
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" {}
|
||||
}
|
||||
|
||||
val generateIdePluginGradleFiles by generator("org.jetbrains.kotlin.generators.imltogradle.MainKt")
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.generators.imltogradle
|
||||
|
||||
open class GradleDependencyNotation(val dependencyNotation: String, val dependencyConfiguration: String? = null) {
|
||||
init {
|
||||
require(dependencyNotation.isNotEmpty())
|
||||
require(dependencyConfiguration?.isNotEmpty() ?: true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val artifactNameSubregex = """([a-zA-Z\-\._1-9]*?)"""
|
||||
|
||||
private val libPathToGradleNotationRegex = """^lib\/$artifactNameSubregex\.jar$""".toRegex()
|
||||
private val pluginsPathToGradleNotationRegex = """^plugins\/$artifactNameSubregex\/.*?$""".toRegex()
|
||||
private val jarToGradleNotationRegex = """^$artifactNameSubregex\.jar$""".toRegex()
|
||||
|
||||
fun fromJarPath(jarPath: String): GradleDependencyNotation? {
|
||||
if (jarPath == "lib/cds/classesLogAgent.jar") {
|
||||
return null // TODO remove hack?
|
||||
}
|
||||
|
||||
if (jarPath.contains("intellij-core.jar")) {
|
||||
return IntellijCoreGradleDependencyNotation
|
||||
}
|
||||
|
||||
fun Regex.firstGroup() = matchEntire(jarPath)?.groupValues?.get(1)
|
||||
|
||||
return pluginsPathToGradleNotationRegex.firstGroup()?.let { IntellijPluginDepGradleDependencyNotation(it) }
|
||||
?: libPathToGradleNotationRegex.firstGroup()?.let { IntellijDepGradleDependencyNotation(it) }
|
||||
?: jarToGradleNotationRegex.firstGroup()?.let { IntellijDepGradleDependencyNotation(it) }
|
||||
?: error("Path $jarPath matches none of the regexes")
|
||||
}
|
||||
}
|
||||
|
||||
object IntellijCoreGradleDependencyNotation : GradleDependencyNotation("intellijCoreDep()", null)
|
||||
|
||||
data class IntellijPluginDepGradleDependencyNotation(val pluginName: String) :
|
||||
GradleDependencyNotation("intellijPluginDep(\"$pluginName\", forIde = true)")
|
||||
|
||||
data class IntellijDepGradleDependencyNotation(val jarName: String) :
|
||||
GradleDependencyNotation("intellijDep(forIde = true)", "{ includeJars(\"$jarName\") }")
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.generators.imltogradle
|
||||
|
||||
import org.jetbrains.jps.model.java.JpsJavaDependencyScope
|
||||
|
||||
interface JpsLikeDependency {
|
||||
fun convertToGradleCall(): String
|
||||
fun normalizedForComparison(): JpsLikeDependency
|
||||
}
|
||||
|
||||
class JpsLikeDependencyWithComment(private val base: JpsLikeDependency, private val comment: String) : JpsLikeDependency {
|
||||
override fun convertToGradleCall(): String {
|
||||
return "${base.convertToGradleCall()} // $comment"
|
||||
}
|
||||
|
||||
override fun normalizedForComparison() = base
|
||||
}
|
||||
|
||||
data class JpsLikeJarDependency(
|
||||
val dependencyNotation: String,
|
||||
val scope: JpsJavaDependencyScope,
|
||||
val dependencyConfiguration: String?,
|
||||
val exported: Boolean
|
||||
) : JpsLikeDependency {
|
||||
override fun convertToGradleCall(): String {
|
||||
val scopeArg = "JpsDepScope.$scope"
|
||||
val exportedArg = "exported = true".takeIf { exported }
|
||||
return "jpsLikeJarDependency(${listOfNotNull(dependencyNotation, scopeArg, dependencyConfiguration, exportedArg).joinToString()})"
|
||||
}
|
||||
|
||||
override fun normalizedForComparison() = this
|
||||
}
|
||||
|
||||
data class JpsLikeModuleDependency(
|
||||
val moduleName: String,
|
||||
val scope: JpsJavaDependencyScope,
|
||||
val exported: Boolean
|
||||
) : JpsLikeDependency {
|
||||
override fun convertToGradleCall(): String {
|
||||
val scopeArg = "JpsDepScope.$scope"
|
||||
val exportedArg = "exported = true".takeIf { exported }
|
||||
return "jpsLikeModuleDependency(${listOfNotNull("\"$moduleName\"", scopeArg, exportedArg).joinToString()})"
|
||||
}
|
||||
|
||||
override fun normalizedForComparison() = this
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.generators.imltogradle
|
||||
|
||||
import com.google.gson.JsonParser
|
||||
import org.jetbrains.jps.model.JpsSimpleElement
|
||||
import org.jetbrains.jps.model.java.JavaResourceRootType
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||
import org.jetbrains.jps.model.java.JpsJavaDependencyScope
|
||||
import org.jetbrains.jps.model.library.JpsLibrary
|
||||
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor
|
||||
import org.jetbrains.jps.model.library.JpsOrderRootType
|
||||
import org.jetbrains.jps.model.module.*
|
||||
import org.jetbrains.kotlin.generators.imltogradle.GradleDependencyNotation.IntellijDepGradleDependencyNotation
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
private lateinit var intellijModuleNameToGradleDependencyNotationsMapping: Map<String, List<GradleDependencyNotation>>
|
||||
private val KOTLIN_REPO_ROOT = File(".").canonicalFile
|
||||
private val INTELLIJ_REPO_ROOT = KOTLIN_REPO_ROOT.resolve("kotlin-ide")
|
||||
|
||||
private val intellijModuleNameToGradleDependencyNotationsMappingManual: List<Pair<String, GradleDependencyNotation>> = listOf(
|
||||
"intellij.platform.jps.build" to GradleDependencyNotation("jpsBuildTest()"),
|
||||
"intellij.platform.structuralSearch" to IntellijDepGradleDependencyNotation("structuralsearch") // for some reason it's absent in json mapping
|
||||
)
|
||||
|
||||
// These modules are used in Kotlin plugin and IDEA doesn't publish artifact of these modules
|
||||
private val intellijModulesForWhichGenerateBuildGradle = listOf(
|
||||
"intellij.platform.debugger.testFramework",
|
||||
"intellij.gradle.tests",
|
||||
"intellij.platform.externalSystem.tests",
|
||||
"intellij.platform.lang.tests",
|
||||
"intellij.platform.testExtensions",
|
||||
"intellij.java.compiler.tests",
|
||||
"intellij.gradle.toolingExtension.tests"
|
||||
)
|
||||
|
||||
val jsonUrlPrefixes = mapOf(
|
||||
"202" to "https://buildserver.labs.intellij.net/guestAuth/repository/download/ijplatform_IjPlatform202_IntellijArtifactMappings/113235432:id",
|
||||
"203" to "https://buildserver.labs.intellij.net/guestAuth/repository/download/ijplatform_IjPlatform203_IntellijArtifactMappings/117989041:id",
|
||||
)
|
||||
|
||||
fun main() {
|
||||
val ijCommunityModuleNameToJpsModuleMapping = INTELLIJ_REPO_ROOT.loadJpsProject().modules.associateBy { it.name }
|
||||
|
||||
val imlFiles = INTELLIJ_REPO_ROOT.walk()
|
||||
.onEnter { it != INTELLIJ_REPO_ROOT.resolve("out") }
|
||||
.filter {
|
||||
it.isFile && it.extension == "iml" &&
|
||||
(it.name.startsWith("kotlin.") || it.nameWithoutExtension in intellijModulesForWhichGenerateBuildGradle)
|
||||
}
|
||||
.toList()
|
||||
|
||||
val ideaMajorVersion = KOTLIN_REPO_ROOT.resolve("local.properties").takeIf { it.exists() }
|
||||
?.inputStream()
|
||||
?.use { Properties().apply { load(it) }.getProperty("attachedIntellijVersion") }
|
||||
?: error("Can't find 'attachedIntellijVersion' in 'local.properties'")
|
||||
|
||||
intellijModuleNameToGradleDependencyNotationsMapping = fetchJsonsFromBuildserver(ideaMajorVersion)
|
||||
.flatMap { jsonStr ->
|
||||
JsonParser.parseString(jsonStr).asJsonArray.mapNotNull { jsonElement ->
|
||||
val jsonObject = jsonElement.asJsonObject
|
||||
val moduleName = jsonObject.get("moduleName")?.asString ?: return@mapNotNull null
|
||||
val jarPath = jsonObject.get("path")?.asString ?: return@mapNotNull null
|
||||
moduleName to jarPath
|
||||
}
|
||||
}
|
||||
.filter { (_, jarPath) -> !jarPath.contains("DatabaseTools") && !jarPath.contains("lib/openapi.jar") }
|
||||
.groupBy(
|
||||
keySelector = { (_, jarPath) -> jarPath },
|
||||
valueTransform = { (moduleName, _) -> moduleName }
|
||||
)
|
||||
.filter { (_, moduleNames) ->
|
||||
moduleNames.all { it in ijCommunityModuleNameToJpsModuleMapping } // filter out ultimate jars
|
||||
}
|
||||
.flatMap { (jarPath, moduleNames) -> moduleNames.map { it to jarPath } }
|
||||
.mapNotNull { (moduleName, jarPath) ->
|
||||
moduleName to (GradleDependencyNotation.fromJarPath(jarPath) ?: return@mapNotNull null)
|
||||
}
|
||||
.plus(intellijModuleNameToGradleDependencyNotationsMappingManual)
|
||||
.groupBy(
|
||||
keySelector = { (moduleName, _) -> moduleName },
|
||||
valueTransform = { (_, dependencyNotation) -> dependencyNotation }
|
||||
)
|
||||
|
||||
imlFiles.mapNotNull { imlFile -> ijCommunityModuleNameToJpsModuleMapping[imlFile.nameWithoutExtension]?.let { imlFile to it } }
|
||||
.forEach { (imlFile, jpsModule) ->
|
||||
imlFile.parentFile.resolve("build.gradle.kts").writeText(convertJpsModule(imlFile, jpsModule))
|
||||
}
|
||||
}
|
||||
|
||||
fun convertJpsLibrary(lib: JpsLibrary, scope: JpsJavaDependencyScope, exported: Boolean): List<JpsLikeDependency> {
|
||||
val mavenRepositoryLibraryDescriptor = lib.properties
|
||||
.safeAs<JpsSimpleElement<*>>()?.data?.safeAs<JpsMavenRepositoryLibraryDescriptor>()
|
||||
|
||||
return when {
|
||||
mavenRepositoryLibraryDescriptor == null -> {
|
||||
lib.getRootUrls(JpsOrderRootType.COMPILED)
|
||||
.map { File(it.removePrefix("jar://").removeSuffix("!/")).relativeTo(KOTLIN_REPO_ROOT) }
|
||||
.map {
|
||||
JpsLikeJarDependency(
|
||||
"files(rootDir.resolve(\"$it\").canonicalPath)",
|
||||
scope,
|
||||
dependencyConfiguration = null,
|
||||
exported = exported
|
||||
)
|
||||
}
|
||||
}
|
||||
lib.name == "kotlinc.kotlin-stdlib-jdk8" -> {
|
||||
listOf(
|
||||
JpsLikeJarDependency("kotlinStdlib()", scope, dependencyConfiguration = null, exported = exported),
|
||||
// TODO remove hack (for some reason we have to specify :kotlin-stdlib-jdk7 explicitly, otherwise compilation doesn't pass)
|
||||
JpsLikeJarDependency("project(\":kotlin-stdlib-jdk7\")", scope, dependencyConfiguration = null, exported = exported)
|
||||
)
|
||||
}
|
||||
lib.name.startsWith("kotlinc.") -> {
|
||||
val artifactId = mavenRepositoryLibraryDescriptor.artifactId
|
||||
val dependencyNotation =
|
||||
if (KOTLIN_REPO_ROOT.resolve("prepare/ide-plugin-dependencies/$artifactId").exists())
|
||||
"project(\":prepare:ide-plugin-dependencies:$artifactId\")"
|
||||
else
|
||||
"project(\":${mavenRepositoryLibraryDescriptor.artifactId}\")"
|
||||
listOf(JpsLikeJarDependency(dependencyNotation, scope, dependencyConfiguration = null, exported = exported))
|
||||
}
|
||||
else -> {
|
||||
val dependencyNotation = "\"${mavenRepositoryLibraryDescriptor.mavenId}\""
|
||||
listOf(JpsLikeJarDependency(dependencyNotation, scope, dependencyConfiguration = null, exported = exported))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun convertIntellijDependencyNotFollowingTransitive(dep: JpsDependencyDescriptor, exported: Boolean): List<JpsLikeDependency> {
|
||||
return when (val moduleOrLibrary = dep.moduleOrLibrary) {
|
||||
is Either.First -> {
|
||||
val moduleName = moduleOrLibrary.value.name
|
||||
if (moduleName in intellijModulesForWhichGenerateBuildGradle) {
|
||||
listOf(JpsLikeModuleDependency(":kotlin-ide.$moduleName", dep.scope, exported))
|
||||
} else {
|
||||
intellijModuleNameToGradleDependencyNotationsMapping[moduleName]
|
||||
.also { if (it == null) println("WARNING: Cannot find GradleDependencyNotation for $moduleName") }
|
||||
?.map { JpsLikeJarDependency(it.dependencyNotation, dep.scope, it.dependencyConfiguration, exported) }
|
||||
?: emptyList()
|
||||
}
|
||||
}
|
||||
is Either.Second -> convertJpsLibrary(moduleOrLibrary.value, dep.scope, exported)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertJpsModuleDependency(dep: JpsModuleDependency): List<JpsLikeDependency> {
|
||||
val moduleName = dep.moduleReference.moduleName
|
||||
return when {
|
||||
moduleName.startsWith("kotlin.") -> {
|
||||
listOf(JpsLikeModuleDependency(":kotlin-ide.$moduleName", dep.scope, dep.isExported))
|
||||
}
|
||||
moduleName.startsWith("intellij.") -> {
|
||||
dep.module.orElse { error("Cannot resolve dependency ${dep.moduleReference.moduleName}") }
|
||||
.flattenExportedTransitiveDependencies()
|
||||
.map { it.copy(scope = it.scope intersectCompileClasspath dep.scope) }
|
||||
.filter { it.scope != JpsJavaDependencyScope.RUNTIME } // We are interested only in transitive dependencies which affect compilation
|
||||
.flatMap { convertIntellijDependencyNotFollowingTransitive(it, dep.isExported).asSequence() }
|
||||
.mapIndexed { index, jpsLikeDependency ->
|
||||
if (index != 0) JpsLikeDependencyWithComment(jpsLikeDependency, "Exported transitive dependency") else jpsLikeDependency
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
else -> error("Cannot convert module dependency to Gradle $dep")
|
||||
}
|
||||
}
|
||||
|
||||
fun convertJpsDependencyElement(dep: JpsDependencyElement): List<JpsLikeDependency> {
|
||||
return when (dep) {
|
||||
is JpsModuleDependency -> convertJpsModuleDependency(dep)
|
||||
is JpsLibraryDependency -> dep.library
|
||||
.orElse { error("Cannot resolve library reference = ${dep.libraryReference}") }
|
||||
.let { convertJpsLibrary(it, dep.scope, dep.isExported) }
|
||||
else -> error("Unknown dependency: $dep")
|
||||
}
|
||||
}
|
||||
|
||||
fun convertJpsModuleSourceRoot(imlFile: File, sourceRoot: JpsModuleSourceRoot): String {
|
||||
return when (sourceRoot.rootType) {
|
||||
is JavaSourceRootType -> "java.srcDir(\"${sourceRoot.file.relativeTo(imlFile.parentFile)}\")"
|
||||
is JavaResourceRootType -> "resources.srcDir(\"${sourceRoot.file.relativeTo(imlFile.parentFile)}\")"
|
||||
else -> error("Unknown sourceRoot = $sourceRoot")
|
||||
}
|
||||
}
|
||||
|
||||
fun convertJpsModule(imlFile: File, jpsModule: JpsModule): String {
|
||||
val (src, test) = jpsModule.sourceRoots
|
||||
.groupBy { it.rootType.isForTests }
|
||||
.mapValues { entry -> entry.value.joinToString("\n") { convertJpsModuleSourceRoot(imlFile, it) } }
|
||||
.let { Pair(it[false] ?: "", it[true] ?: "") }
|
||||
|
||||
val mavenRepos = INTELLIJ_REPO_ROOT.resolve(".idea/jarRepositories.xml").readXml().traverseChildren()
|
||||
.filter { it.getAttributeValue("name") == "url" }
|
||||
.map { it.getAttributeValue("value")!! }
|
||||
.map { "maven { setUrl(\"$it\") }" }
|
||||
.joinToString("\n")
|
||||
|
||||
fun File.compilerArgsFromIml() = readXml().traverseChildren().singleOrNull { it.name == "compilerSettings" }?.children?.single()
|
||||
?.getAttributeValue("value")
|
||||
|
||||
fun File.compilerArgsFromProjectSettings() = readXml().traverseChildren()
|
||||
.singleOrNull { it.getAttributeValue("name") == "additionalArguments" }?.getAttributeValue("value")
|
||||
|
||||
val compilerArgs = imlFile.compilerArgsFromIml()
|
||||
.orElse { INTELLIJ_REPO_ROOT.resolve(".idea/kotlinc.xml").compilerArgsFromProjectSettings() }
|
||||
?.split(" ")
|
||||
?.joinToString { "\"$it\"" }
|
||||
?: ""
|
||||
|
||||
val deps = jpsModule.dependencies.flatMap { convertJpsDependencyElement(it) }
|
||||
.distinctBy { it.normalizedForComparison() }
|
||||
.joinToString("\n") { it.convertToGradleCall() }
|
||||
return """
|
||||
|// GENERATED build.gradle.kts
|
||||
|// GENERATED BY ${imlFile.name}
|
||||
|// USE `./gradlew generateIdeaGradleFiles` TO REGENERATE THIS FILE
|
||||
|
|
||||
|plugins {
|
||||
| kotlin("jvm")
|
||||
| id("jps-compatible")
|
||||
|}
|
||||
|
|
||||
|repositories {
|
||||
| $mavenRepos
|
||||
|}
|
||||
|
|
||||
|dependencies {
|
||||
| implementation(toolsJarApi())
|
||||
| $deps
|
||||
|}
|
||||
|
|
||||
|configurations.all {
|
||||
| exclude(module = "tests-common") // Avoid classes with same FQN clashing
|
||||
|}
|
||||
|
|
||||
|sourceSets {
|
||||
| "main" {
|
||||
| $src
|
||||
| }
|
||||
| "test" {
|
||||
| $test
|
||||
| }
|
||||
|}
|
||||
|
|
||||
|java {
|
||||
| toolchain {
|
||||
| languageVersion.set(JavaLanguageVersion.of(11))
|
||||
| }
|
||||
|}
|
||||
|
|
||||
|tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
|
||||
| kotlinOptions.freeCompilerArgs = listOf($compilerArgs)
|
||||
| kotlinOptions.jdkHome = rootProject.extra["JDK_11"] as String
|
||||
| kotlinOptions.useOldBackend = true // KT-45697
|
||||
|}
|
||||
|
|
||||
|testsJar()
|
||||
""".trimMarginWithInterpolations()
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.generators.imltogradle
|
||||
|
||||
import org.jdom.Element
|
||||
import org.jdom.input.SAXBuilder
|
||||
import org.jetbrains.jps.model.JpsElementFactory
|
||||
import org.jetbrains.jps.model.JpsProject
|
||||
import org.jetbrains.jps.model.java.JpsJavaDependencyScope
|
||||
import org.jetbrains.jps.model.java.impl.JpsJavaExtensionServiceImpl
|
||||
import org.jetbrains.jps.model.module.JpsDependencyElement
|
||||
import org.jetbrains.jps.model.module.JpsLibraryDependency
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import org.jetbrains.jps.model.module.JpsModuleDependency
|
||||
import org.jetbrains.jps.model.serialization.JpsProjectLoader
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
fun String.trimMarginWithInterpolations(): String {
|
||||
val regex = Regex("""^(\s*\|)(\s*).*$""")
|
||||
val out = mutableListOf<String>()
|
||||
var prevIndent = ""
|
||||
for (line in lines()) {
|
||||
val matchResult = regex.matchEntire(line)
|
||||
if (matchResult != null) {
|
||||
out.add(line.removePrefix(matchResult.groupValues[1]))
|
||||
prevIndent = matchResult.groupValues[2]
|
||||
} else {
|
||||
out.add(prevIndent + line)
|
||||
}
|
||||
}
|
||||
return out.joinToString("\n").trim()
|
||||
}
|
||||
|
||||
fun File.readXml(): Element {
|
||||
return inputStream().use { SAXBuilder().build(it).rootElement }
|
||||
}
|
||||
|
||||
suspend fun SequenceScope<Element>.visit(element: Element) {
|
||||
element.children.forEach { visit(it) }
|
||||
yield(element)
|
||||
}
|
||||
fun Element.traverseChildren(): Sequence<Element> {
|
||||
return sequence { visit(this@traverseChildren) }
|
||||
}
|
||||
|
||||
inline fun <reified T> Any?.safeAs(): T? {
|
||||
return this as? T
|
||||
}
|
||||
|
||||
val JpsDependencyElement.scope: JpsJavaDependencyScope
|
||||
get() = JpsJavaExtensionServiceImpl.getInstance().getDependencyExtension(this)?.scope
|
||||
?: error("Cannot get dependency scope for $this")
|
||||
|
||||
val JpsDependencyElement.isExported: Boolean
|
||||
get() = JpsJavaExtensionServiceImpl.getInstance().getDependencyExtension(this)?.isExported
|
||||
?: error("Cannot get dependency isExported for $this")
|
||||
|
||||
fun File.loadJpsProject(): JpsProject {
|
||||
val model = JpsElementFactory.getInstance().createModel()
|
||||
val project = model.project
|
||||
JpsProjectLoader.loadProject(project, mapOf(), this.canonicalPath)
|
||||
return project
|
||||
}
|
||||
|
||||
sealed class Either<out A, out B> {
|
||||
data class First<out A>(val value: A) : Either<A, Nothing>()
|
||||
data class Second<out B>(val value: B) : Either<Nothing, B>()
|
||||
}
|
||||
|
||||
val <T, A : T, B : T> Either<A, B>.value: T
|
||||
get() = when (this) {
|
||||
is Either.First -> this.value
|
||||
is Either.Second -> this.value
|
||||
}
|
||||
|
||||
inline fun <T> T?.orElse(block: () -> T): T = this ?: block()
|
||||
|
||||
val JpsModule.dependencies: List<JpsDependencyElement>
|
||||
get() = dependenciesList.dependencies.filter { it is JpsModuleDependency || it is JpsLibraryDependency }
|
||||
|
||||
fun fetchJsonsFromBuildserver(ideaMajorVersion: String): List<String> {
|
||||
require(ideaMajorVersion.length == 3 && ideaMajorVersion.all { it.isDigit() }) {
|
||||
"attachedIntellijVersion='$ideaMajorVersion' must be 3 length all digit string"
|
||||
}
|
||||
val urlPrefix = jsonUrlPrefixes[ideaMajorVersion] ?: error("'$ideaMajorVersion' platform is absent in mapping")
|
||||
return listOf(
|
||||
"$urlPrefix/ideaIU-project-structure-mapping.json",
|
||||
"$urlPrefix/intellij-core-project-structure-mapping.json"
|
||||
).map { url ->
|
||||
try {
|
||||
URL(url).readText()
|
||||
} catch (ex: Throwable) {
|
||||
error("Can't access $url. Is VPN on?")
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.generators.imltogradle
|
||||
|
||||
import org.jetbrains.jps.model.java.JpsJavaDependencyScope
|
||||
import org.jetbrains.jps.model.library.JpsLibrary
|
||||
import org.jetbrains.jps.model.module.JpsDependencyElement
|
||||
import org.jetbrains.jps.model.module.JpsLibraryDependency
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import org.jetbrains.jps.model.module.JpsModuleDependency
|
||||
import java.util.*
|
||||
|
||||
data class JpsDependencyDescriptor(val moduleOrLibrary: Either<JpsModule, JpsLibrary>, val scope: JpsJavaDependencyScope) {
|
||||
companion object {
|
||||
fun from(dep: JpsDependencyElement): JpsDependencyDescriptor? {
|
||||
val moduleOrLibrary = when (dep) {
|
||||
is JpsModuleDependency -> dep.module
|
||||
.orElse { error("Cannot resolve module reference = ${dep.moduleReference}") }
|
||||
.let { Either.First(it) }
|
||||
is JpsLibraryDependency -> dep.library
|
||||
.orElse { error("Cannot resolve library reference = ${dep.libraryReference}") }
|
||||
.let { Either.Second(it) }
|
||||
else -> return null
|
||||
}
|
||||
return JpsDependencyDescriptor(moduleOrLibrary, dep.scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun JpsModule.flattenExportedTransitiveDependencies(): Sequence<JpsDependencyDescriptor> {
|
||||
val visitedModuleNames = HashSet<String>()
|
||||
val toVisit = PriorityQueue<JpsDependencyDescriptor>(
|
||||
compareBy<JpsDependencyDescriptor, JpsJavaDependencyScope>(JpsDependencyScopeCompileClasspathComparator.reversed()) { it.scope }
|
||||
.thenComparing(compareBy { it.moduleOrLibrary.value.name })
|
||||
)
|
||||
|
||||
// Dijkstra's modified algorithm: intersectCompileClasspath is like sum
|
||||
suspend fun SequenceScope<JpsDependencyDescriptor>.visit(current: JpsDependencyDescriptor) {
|
||||
when (val moduleOrLibrary = current.moduleOrLibrary) {
|
||||
is Either.First -> {
|
||||
val jpsModule = moduleOrLibrary.value
|
||||
if (!visitedModuleNames.add(jpsModule.name)) {
|
||||
return
|
||||
}
|
||||
yield(current)
|
||||
val elements = jpsModule.dependencies
|
||||
.filter { it.isExported }
|
||||
.map { JpsDependencyDescriptor.from(it)!! }
|
||||
.map { it.copy(scope = it.scope intersectCompileClasspath current.scope) }
|
||||
toVisit.addAll(elements)
|
||||
while (toVisit.isNotEmpty()) {
|
||||
visit(toVisit.poll())
|
||||
}
|
||||
}
|
||||
is Either.Second -> yield(current)
|
||||
}
|
||||
}
|
||||
return sequence {
|
||||
visit(
|
||||
JpsDependencyDescriptor(
|
||||
Either.First(this@flattenExportedTransitiveDependencies),
|
||||
JpsDependencyScopeCompileClasspathComparator.ascending.last()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private object JpsDependencyScopeCompileClasspathComparator : Comparator<JpsJavaDependencyScope> {
|
||||
// Let's assume that module A depends on module B (A->B notation)
|
||||
// A[src] notation means "source root" of module A. A[test] notation means "test root" of module A.
|
||||
// Keeping this in mind let's consider different types of dependencies:
|
||||
val ascending = listOf(
|
||||
JpsJavaDependencyScope.RUNTIME, // Doesn't establish any compile time dependencies
|
||||
JpsJavaDependencyScope.TEST, // Compile time dependencies are: A[test]->B[src], A[test]->B[test] (so TEST > RUNTIME)
|
||||
JpsJavaDependencyScope.PROVIDED, // Compile time dependencies are like in TEST + A[src]->B[src] (so PROVIDED > TEST)
|
||||
JpsJavaDependencyScope.COMPILE, /* Compile time dependencies are like in PROVIDED
|
||||
(so it actually doesn't matter in which order PROVIDED and COMPILE go) */
|
||||
)
|
||||
|
||||
override fun compare(first: JpsJavaDependencyScope?, second: JpsJavaDependencyScope?): Int {
|
||||
val firstIndex = ascending.indexOf(first).takeIf { it != -1 } ?: error("Unknown $first")
|
||||
val secondIndex = ascending.indexOf(second).takeIf { it != -1 } ?: error("Unknown $second")
|
||||
return firstIndex.compareTo(secondIndex)
|
||||
}
|
||||
}
|
||||
|
||||
infix fun JpsJavaDependencyScope.intersectCompileClasspath(other: JpsJavaDependencyScope): JpsJavaDependencyScope {
|
||||
return minOf(this, other, JpsDependencyScopeCompileClasspathComparator)
|
||||
}
|
||||
@@ -194,6 +194,7 @@ include ":benchmarks",
|
||||
":compiler:tests-different-jdk",
|
||||
":compiler:tests-spec",
|
||||
":generators",
|
||||
":generators:ide-iml-to-gradle-generator",
|
||||
":generators:test-generator",
|
||||
":tools:kotlinp",
|
||||
":kotlin-project-model",
|
||||
|
||||
Reference in New Issue
Block a user