Configuration: Add migration of JPS project to new non-jvm source roots

This commit is contained in:
Alexey Sedunov
2018-04-11 12:57:22 +03:00
parent 9fa26c83b6
commit d2bfb8caec
11 changed files with 271 additions and 22 deletions
@@ -49,7 +49,7 @@ object JsLibraryStdDetectionUtil {
return JarUtil.getJarAttribute(VfsUtilCore.virtualToIoFile(jar), Attributes.Name.IMPLEMENTATION_VERSION)
}
private fun getJsStdLibJar(classesRoots: List<VirtualFile>): VirtualFile? {
fun getJsStdLibJar(classesRoots: List<VirtualFile>): VirtualFile? {
for (root in classesRoots) {
if (root.fileSystem.protocol !== StandardFileSystems.JAR_PROTOCOL) continue
@@ -42,15 +42,18 @@ object CommonLibraryKind : PersistentLibraryKind<DummyLibraryProperties>("kotlin
override fun createDefaultProperties() = DummyLibraryProperties.INSTANCE!!
}
fun getLibraryPlatform(project: Project, library: Library): TargetPlatform {
library as? LibraryEx ?: return JvmPlatform
if (library.isDisposed) return JvmPlatform
return when (library.effectiveKind(project)) {
val PersistentLibraryKind<*>?.platform: TargetPlatform
get() = when (this) {
JSLibraryKind -> JsPlatform
CommonLibraryKind -> TargetPlatform.Common
else -> JvmPlatform
}
fun getLibraryPlatform(project: Project, library: Library): TargetPlatform {
library as? LibraryEx ?: return JvmPlatform
if (library.isDisposed) return JvmPlatform
return library.effectiveKind(project).platform
}
fun detectLibraryKind(roots: Array<VirtualFile>): PersistentLibraryKind<*>? {
@@ -89,14 +92,13 @@ private fun detectLibraryKindFromJarContents(jarRoot: VirtualFile): PersistentLi
return result
}
fun getLibraryJarVersion(library: Library, jarPattern: Pattern): String? {
for (file in library.getFiles(OrderRootType.CLASSES)) {
if (jarPattern.matcher(file.name).matches()) {
return JarUtil.getJarAttribute(VfsUtilCore.virtualToIoFile(file), Attributes.Name.IMPLEMENTATION_VERSION)
}
}
return null
fun getLibraryJar(roots: Array<VirtualFile>, jarPattern: Pattern): VirtualFile? {
return roots.firstOrNull { jarPattern.matcher(it.name).matches() }
}
fun getCommonRuntimeLibraryVersion(library: Library) =
getLibraryJarVersion(library, PathUtil.KOTLIN_STDLIB_COMMON_JAR_PATTERN)
fun getLibraryJarVersion(library: Library, jarPattern: Pattern): String? {
val libraryJar = getLibraryJar(library.getFiles(OrderRootType.CLASSES), jarPattern) ?: return null
return JarUtil.getJarAttribute(VfsUtilCore.virtualToIoFile(libraryJar), Attributes.Name.IMPLEMENTATION_VERSION)
}
fun getCommonRuntimeLibraryVersion(library: Library) = getLibraryJarVersion(library, PathUtil.KOTLIN_STDLIB_COMMON_JAR_PATTERN)
@@ -25,6 +25,7 @@ import org.jdom.Element
import org.jdom.Text
import org.jetbrains.kotlin.cli.common.arguments.*
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import java.lang.reflect.Modifier
import kotlin.reflect.KClass
import kotlin.reflect.full.superclasses
@@ -93,11 +94,15 @@ private fun readV1Config(element: Element): KotlinFacetSettings {
}
}
fun Element.getFacetPlatformByConfigurationElement(): TargetPlatformKind<*> {
val platformName = getAttributeValue("platform")
return TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM
}
private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings {
return KotlinFacetSettings().apply {
element.getAttributeValue("useProjectSettings")?.let { useProjectSettings = it.toBoolean() }
val platformName = element.getAttributeValue("platform")
val platformKind = TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM
val platformKind = element.getFacetPlatformByConfigurationElement()
element.getChild("implements")?.let {
val items = it.getChildren("implement")
implementedModuleNames = if (items.isNotEmpty()) {
+2
View File
@@ -824,6 +824,8 @@
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$Resource"/>
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$TestResource"/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.roots.KotlinNonJvmSourceRootConverterProvider"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>
+2
View File
@@ -824,6 +824,8 @@
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$Resource"/>
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$TestResource"/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.roots.KotlinNonJvmSourceRootConverterProvider"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>
+2
View File
@@ -824,6 +824,8 @@
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$Resource"/>
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$TestResource"/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.roots.KotlinNonJvmSourceRootConverterProvider"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>
+2
View File
@@ -825,6 +825,8 @@
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$Resource"/>
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$TestResource"/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.roots.KotlinNonJvmSourceRootConverterProvider"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>
+2
View File
@@ -824,6 +824,8 @@
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$Resource"/>
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$TestResource"/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.roots.KotlinNonJvmSourceRootConverterProvider"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>
+2
View File
@@ -824,6 +824,8 @@
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$Resource"/>
<projectStructure.sourceRootEditHandler implementation="org.jetbrains.kotlin.idea.roots.ui.KotlinModuleSourceRootEditHandler$TestResource"/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.roots.KotlinNonJvmSourceRootConverterProvider"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>
@@ -0,0 +1,220 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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.idea.roots
import com.intellij.conversion.*
import com.intellij.conversion.impl.ConversionContextImpl
import com.intellij.conversion.impl.ModuleSettingsImpl
import com.intellij.openapi.roots.ExternalProjectSystemRegistry
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.impl.ContentEntryImpl
import com.intellij.openapi.roots.impl.OrderEntryFactory.ORDER_ENTRY_TYPE_ATTR
import com.intellij.openapi.roots.impl.SourceFolderImpl
import com.intellij.openapi.roots.impl.libraries.ApplicationLibraryTable
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.impl.libraries.LibraryImpl
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryKind
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.openapi.roots.libraries.PersistentLibraryKind
import com.intellij.openapi.vfs.JarFileSystem
import com.intellij.openapi.vfs.VirtualFile
import org.jdom.Element
import org.jetbrains.jps.model.JpsElement
import org.jetbrains.jps.model.JpsElementFactory
import org.jetbrains.jps.model.java.JavaResourceRootType
import org.jetbrains.jps.model.java.JavaSourceRootType
import org.jetbrains.jps.model.module.JpsModuleSourceRootType
import org.jetbrains.jps.model.module.JpsTypedModuleSourceRoot
import org.jetbrains.jps.model.serialization.facet.JpsFacetSerializer
import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer
import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer.*
import org.jetbrains.kotlin.config.getFacetPlatformByConfigurationElement
import org.jetbrains.kotlin.idea.facet.KotlinFacetType
import org.jetbrains.kotlin.idea.framework.*
import org.jetbrains.kotlin.idea.refactoring.toVirtualFile
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.utils.PathUtil
class KotlinNonJvmSourceRootConverterProvider : ConverterProvider("kotlin-non-jvm-source-roots") {
companion object {
private val rootTypesToMigrate: List<JpsModuleSourceRootType<*>> = listOf(
JavaSourceRootType.SOURCE,
JavaSourceRootType.TEST_SOURCE,
JavaResourceRootType.RESOURCE,
JavaResourceRootType.TEST_RESOURCE
)
private val TargetPlatform.stdlibDetector: ((Array<VirtualFile>) -> Boolean)?
get() = when (this) {
is JvmPlatform -> { roots -> JavaRuntimeDetectionUtil.getRuntimeJar(roots.toList()) != null }
is JsPlatform -> { roots -> JsLibraryStdDetectionUtil.getJsStdLibJar(roots.toList()) != null }
is TargetPlatform.Common -> { roots -> getLibraryJar(roots, PathUtil.KOTLIN_STDLIB_COMMON_JAR_PATTERN) != null }
else -> null
}
}
sealed class LibInfo {
class ByXml(
private val element: Element,
private val conversionContext: ConversionContext,
private val moduleSettings: ModuleSettings
) : LibInfo() {
override val explicitKind: PersistentLibraryKind<*>?
get() = LibraryKind.findById(element.getAttributeValue("type")) as? PersistentLibraryKind<*>
override fun getRoots(): Array<VirtualFile> {
val contextImpl = conversionContext as? ConversionContextImpl ?: return VirtualFile.EMPTY_ARRAY
val moduleSettingsImpl = moduleSettings as? ModuleSettingsImpl ?: return VirtualFile.EMPTY_ARRAY
return contextImpl
.getClassRoots(element, moduleSettingsImpl)
.mapNotNull { it.toVirtualFile()?.let { JarFileSystem.getInstance().getJarRootForLocalFile(it) } }
.toTypedArray()
}
}
class ByLibrary(private val library: Library) : LibInfo() {
override val explicitKind: PersistentLibraryKind<*>?
get() = (library as? LibraryEx)?.kind
override fun getRoots() = library.getFiles(OrderRootType.CLASSES)
}
abstract val explicitKind: PersistentLibraryKind<*>?
abstract fun getRoots(): Array<VirtualFile>
val platform by lazy {
val explicitKind = explicitKind
val kind = if (explicitKind is KotlinLibraryKind) explicitKind else detectLibraryKind(getRoots())
kind?.platform ?: JvmPlatform
}
val isStdlib: Boolean
get() = platform.stdlibDetector?.invoke(getRoots()) ?: false
}
class ConverterImpl(private val context: ConversionContext) : ProjectConverter() {
private val projectLibrariesByName by lazy {
context.projectLibrariesSettings.projectLibraries.groupBy { it.getAttributeValue(LibraryImpl.LIBRARY_NAME_ATTR) }
}
private fun findGlobalLibrary(name: String) = ApplicationLibraryTable.getApplicationTable().getLibraryByName(name)
private fun findProjectLibrary(name: String) = projectLibrariesByName[name]?.firstOrNull()
private fun createLibInfo(orderEntryElement: Element, moduleSettings: ModuleSettings): LibInfo? {
val entryType = orderEntryElement.getAttributeValue(ORDER_ENTRY_TYPE_ATTR)
return when (entryType) {
JpsModuleRootModelSerializer.MODULE_LIBRARY_TYPE -> {
orderEntryElement.getChild(LIBRARY_TAG)?.let { LibInfo.ByXml(it, context, moduleSettings) }
}
JpsModuleRootModelSerializer.LIBRARY_TYPE -> {
val libraryName = orderEntryElement.getAttributeValue(NAME_ATTRIBUTE) ?: return null
val level = orderEntryElement.getAttributeValue(LEVEL_ATTRIBUTE)
when (level) {
LibraryTablesRegistrar.PROJECT_LEVEL ->
findProjectLibrary(libraryName)?.let { LibInfo.ByXml(it, context, moduleSettings) }
LibraryTablesRegistrar.APPLICATION_LEVEL ->
findGlobalLibrary(libraryName)?.let { LibInfo.ByLibrary(it) }
else ->
null
}
}
else -> null
}
}
override fun createModuleFileConverter(): ConversionProcessor<ModuleSettings> {
return object : ConversionProcessor<ModuleSettings>() {
private fun ModuleSettings.detectPlatformByFacet() =
getFacetElement(KotlinFacetType.ID)
?.getChild(JpsFacetSerializer.CONFIGURATION_TAG)
?.getFacetPlatformByConfigurationElement()
?.asTargetPlatform()
private fun ModuleSettings.detectPlatformByDependencies(): TargetPlatform? {
var hasCommonStdlib = false
orderEntries
.asSequence()
.mapNotNull { createLibInfo(it, this) }
.forEach {
val platform = it.platform
when (platform) {
is TargetPlatform.Common -> {
if (!hasCommonStdlib && it.isStdlib) {
hasCommonStdlib = true
}
}
else -> {
if (it.isStdlib) return platform
}
}
}
return if (hasCommonStdlib) TargetPlatform.Common else null
}
private fun ModuleSettings.detectPlatform(): TargetPlatform {
return detectPlatformByFacet()
?: detectPlatformByDependencies()
?: JvmPlatform
}
private fun ModuleSettings.getSourceFolderElements(): List<Element> {
val rootManagerElement = getComponentElement(ModuleSettings.MODULE_ROOT_MANAGER_COMPONENT) ?: return emptyList()
return rootManagerElement
.getChildren(ContentEntryImpl.ELEMENT_NAME)
.flatMap { it.getChildren(SourceFolderImpl.ELEMENT_NAME) }
}
private fun ModuleSettings.isExternalModule(): Boolean {
return when {
rootElement.getAttributeValue(ExternalProjectSystemRegistry.EXTERNAL_SYSTEM_ID_KEY) != null -> true
rootElement.getAttributeValue(ExternalProjectSystemRegistry.IS_MAVEN_MODULE_KEY)?.toBoolean() ?: false -> true
else -> false
}
}
override fun isConversionNeeded(settings: ModuleSettings): Boolean {
if (settings.isExternalModule()) return false
val targetPlatform = settings.detectPlatform()
if (targetPlatform == JvmPlatform) return false
return settings.getSourceFolderElements().any {
JpsModuleRootModelSerializer.loadSourceRoot(it).rootType in rootTypesToMigrate
}
}
override fun process(settings: ModuleSettings) {
for (sourceFolder in settings.getSourceFolderElements()) {
val contentRoot = sourceFolder.parent as? Element ?: continue
val oldSourceRoot = JpsModuleRootModelSerializer.loadSourceRoot(sourceFolder)
val url = sourceFolder.getAttributeValue(JpsModuleRootModelSerializer.URL_ATTRIBUTE)
val (newRootType, data) = oldSourceRoot.getMigratedSourceRootTypeWithProperties() ?: continue
@Suppress("UNCHECKED_CAST")
val newSourceRoot = JpsElementFactory.getInstance().createModuleSourceRoot(url, newRootType, data)
as? JpsTypedModuleSourceRoot<JpsElement> ?: continue
contentRoot.removeContent(sourceFolder)
JpsModuleRootModelSerializer.saveSourceRoot(contentRoot, url, newSourceRoot)
}
}
}
}
}
override fun getConversionDescription() = "Update source roots for non-JVM modules in Kotlin project"
override fun createConverter(context: ConversionContext) = ConverterImpl(context)
}
@@ -6,19 +6,23 @@
package org.jetbrains.kotlin.idea.roots
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.openapi.roots.SourceFolder
import org.jetbrains.jps.model.JpsElement
import org.jetbrains.jps.model.ex.JpsElementBase
import org.jetbrains.jps.model.java.JavaResourceRootType
import org.jetbrains.jps.model.java.JavaSourceRootType
import org.jetbrains.jps.model.module.JpsModuleSourceRoot
import org.jetbrains.jps.model.module.JpsModuleSourceRootType
import org.jetbrains.kotlin.config.KotlinResourceRootType
import org.jetbrains.kotlin.config.KotlinSourceRootType
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
private fun SourceFolder.getOrCreateProperties() =
jpsElement.getProperties(rootType)?.also { (it as? JpsElementBase<*>)?.setParent(null) } ?: rootType.createDefaultProperties()
private fun JpsModuleSourceRoot.getOrCreateProperties() =
getProperties(rootType)?.also { (it as? JpsElementBase<*>)?.setParent(null) } ?: rootType.createDefaultProperties()
private fun SourceFolder.getNewSourceRootTypeWithProperties(): Pair<JpsModuleSourceRootType<JpsElement>, JpsElement>? {
fun JpsModuleSourceRoot.getMigratedSourceRootTypeWithProperties(): Pair<JpsModuleSourceRootType<JpsElement>, JpsElement>? {
val currentRootType = rootType
@Suppress("UNCHECKED_CAST")
val newSourceRootType: JpsModuleSourceRootType<JpsElement> = when (currentRootType) {
@@ -34,10 +38,16 @@ private fun SourceFolder.getNewSourceRootTypeWithProperties(): Pair<JpsModuleSou
fun migrateNonJvmSourceFolders(modifiableRootModel: ModifiableRootModel) {
for (contentEntry in modifiableRootModel.contentEntries) {
for (sourceFolder in contentEntry.sourceFolders) {
val (newSourceRootType, properties) = sourceFolder.getNewSourceRootTypeWithProperties() ?: continue
val (newSourceRootType, properties) = sourceFolder.jpsElement.getMigratedSourceRootTypeWithProperties() ?: continue
val url = sourceFolder.url
contentEntry.removeSourceFolder(sourceFolder)
contentEntry.addSourceFolder(url, newSourceRootType, properties)
}
}
}
fun TargetPlatformKind<*>.asTargetPlatform() = when (this) {
is TargetPlatformKind.Jvm -> JvmPlatform
is TargetPlatformKind.JavaScript -> JsPlatform
is TargetPlatformKind.Common -> TargetPlatform.Common
}