From f6d9e53b669a68c9e4c0d0295c928f8ebf1fce45 Mon Sep 17 00:00:00 2001 From: Yaroslav Chernyshev Date: Wed, 14 Jul 2021 13:53:00 +0300 Subject: [PATCH] [CHERRY PICKED FROM IJ] [Import] Introduce new version of KotlinFacetSettings, improve serialization of compiler arguments ^KTIJ-13451 Fixed GitOrigin-RevId: 17ff1ea6c947b7582b445004959c42e54819d31a Original commit: https://github.com/JetBrains/intellij-community/commit/14bd2509ad37f0c818f6753b097d4c138fba5d89 --- .../CompilerArgumentsContentProspector.kt | 46 +++ .../CompilerArgumentsDeserializer.kt | 109 ++++++++ .../arguments/CompilerArgumentsSerializer.kt | 115 ++++++++ .../kotlin/arguments/serializationUtils.kt | 18 ++ .../kotlin/config/KotlinFacetSettings.kt | 2 +- .../kotlin/config/facetSerialization.kt | 55 ++-- .../CompilerArgumentsContentProspectorTest.kt | 263 ++++++++++++++++++ .../CompilerArgumentsSerializationTest.kt | 169 +++++++++++ 8 files changed, 756 insertions(+), 21 deletions(-) create mode 100644 jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsContentProspector.kt create mode 100644 jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsDeserializer.kt create mode 100644 jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsSerializer.kt create mode 100644 jps/jps-common/src/org/jetbrains/kotlin/arguments/serializationUtils.kt create mode 100644 jps/jps-common/test/CompilerArgumentsContentProspectorTest.kt create mode 100644 jps/jps-common/test/CompilerArgumentsSerializationTest.kt diff --git a/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsContentProspector.kt b/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsContentProspector.kt new file mode 100644 index 00000000000..ab25c7ec55e --- /dev/null +++ b/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsContentProspector.kt @@ -0,0 +1,46 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.kotlin.arguments + +import org.jetbrains.kotlin.cli.common.arguments.Argument +import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments +import org.jetbrains.kotlin.cli.common.arguments.InternalArgument +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 +import kotlin.reflect.KType +import kotlin.reflect.full.memberProperties + +object CompilerArgumentsContentProspector { + private val argumentPropertiesCache: MutableMap, List>> = + mutableMapOf() + + private val flagArgumentPropertiesCache: MutableMap, List>> = + mutableMapOf() + + private val stringArgumentPropertiesCache: MutableMap, List>> = + mutableMapOf() + + private val arrayArgumentPropertiesCache: MutableMap, List?>>> = + mutableMapOf() + + private fun getCompilerArguments(kClass: KClass) = argumentPropertiesCache.getOrPut(kClass) { + kClass.memberProperties.filter { prop -> prop.annotations.singleOrNull { it is Argument } != null } + } + + private inline fun List>.filterByReturnType(predicate: (KType?) -> Boolean) = + filter { predicate(it.returnType) }.mapNotNull { it.safeAs>() } + + fun getFlagCompilerArgumentProperties(kClass: KClass): List> = + flagArgumentPropertiesCache.getOrPut(kClass) { getCompilerArguments(kClass).filterByReturnType { it?.classifier == Boolean::class } } + + fun getStringCompilerArgumentProperties(kClass: KClass): List> = + stringArgumentPropertiesCache.getOrPut(kClass) { getCompilerArguments(kClass).filterByReturnType { it?.classifier == String::class } } + + fun getArrayCompilerArgumentProperties(kClass: KClass): List?>> = + arrayArgumentPropertiesCache.getOrPut(kClass) { getCompilerArguments(kClass).filterByReturnType { it?.classifier?.javaClass?.isArray == true } } + + val freeArgsProperty: KProperty1> + get() = CommonToolArguments::freeArgs + val internalArgumentsProperty: KProperty1> + get() = CommonToolArguments::internalArguments +} \ No newline at end of file diff --git a/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsDeserializer.kt b/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsDeserializer.kt new file mode 100644 index 00000000000..ad7f48c2716 --- /dev/null +++ b/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsDeserializer.kt @@ -0,0 +1,109 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.kotlin.arguments + +import org.jdom.Element +import org.jdom.Text +import org.jetbrains.kotlin.cli.common.arguments.ArgumentParseErrors +import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments +import org.jetbrains.kotlin.cli.common.arguments.InternalArgument +import org.jetbrains.kotlin.cli.common.arguments.LanguageSettingsParser +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import java.io.File +import kotlin.reflect.KMutableProperty1 + +interface CompilerArgumentsDeserializer { + val compilerArguments: T + fun deserializeFrom(element: Element) +} + +class CompilerArgumentsDeserializerV5(override val compilerArguments: T) : CompilerArgumentsDeserializer { + override fun deserializeFrom(element: Element) { + val flagArgumentsByName = readFlagArguments(element) + val flagArgumentsPropertiesMap = CompilerArgumentsContentProspector.getFlagCompilerArgumentProperties(compilerArguments::class) + .associateBy { it.name } + flagArgumentsByName.forEach { (name, value) -> + val mutableProp = flagArgumentsPropertiesMap[name].safeAs>() ?: return@forEach + mutableProp.set(compilerArguments, value) + } + + val stringArgumentsByName = readStringArguments(element) + val stringArgumentPropertiesMap = CompilerArgumentsContentProspector.getStringCompilerArgumentProperties(compilerArguments::class) + .associateBy { it.name } + stringArgumentsByName.forEach { (name, arg) -> + val mutableProp = stringArgumentPropertiesMap[name].safeAs>() ?: return@forEach + mutableProp.set(compilerArguments, arg) + } + val arrayArgumentsByName = readArrayArguments(element) + val arrayArgumentPropertiesMap = CompilerArgumentsContentProspector.getArrayCompilerArgumentProperties(compilerArguments::class) + .associateBy { it.name } + arrayArgumentsByName.forEach { (name, arr) -> + val mutableProp = arrayArgumentPropertiesMap[name].safeAs?>>() ?: return@forEach + mutableProp.set(compilerArguments, arr) + } + val freeArgs = readElementsList(element, FREE_ARGS_ROOT_ELEMENTS_NAME, FREE_ARGS_ELEMENT_NAME) + CompilerArgumentsContentProspector.freeArgsProperty.also { + val mutableProp = it.safeAs>>() ?: return@also + mutableProp.set(compilerArguments, freeArgs) + } + + val internalArguments = readElementsList(element, INTERNAL_ARGS_ROOT_ELEMENTS_NAME, INTERNAL_ARGS_ELEMENT_NAME) + .mapNotNull { parseInternalArgument(it) } + CompilerArgumentsContentProspector.internalArgumentsProperty.safeAs>>() + ?.set(compilerArguments, internalArguments) + } + + companion object { + private fun readElementConfigurable(element: Element, rootElementName: String, configurable: Element.() -> Unit) { + element.getChild(rootElementName)?.apply { configurable(this) } + } + + private fun readStringArguments(element: Element): Map = mutableMapOf().also { + readElementConfigurable(element, STRING_ROOT_ELEMENTS_NAME) { + getChildren(STRING_ELEMENT_NAME).forEach { child -> + val name = child.getAttribute(NAME_ATTR_NAME)?.value ?: return@forEach + val arg = if (name == "classpath") + readElementsList(child, ARGS_ATTR_NAME, ARG_ATTR_NAME).joinToString(File.pathSeparator) + else child.getAttribute(ARG_ATTR_NAME)?.value ?: return@forEach + it += name to arg + } + } + } + + private fun readFlagArguments(element: Element): Map = mutableMapOf().also { + readElementConfigurable(element, FLAG_ROOT_ELEMENTS_NAME) { + getChildren(FLAG_ELEMENT_NAME).forEach { child -> + val name = child.getAttribute(NAME_ATTR_NAME)?.value ?: return@forEach + val arg = child.getAttribute(ARG_ATTR_NAME)?.booleanValue ?: return@forEach + it += name to arg + } + } + } + + private fun readElementsList(element: Element, rootElementName: String, elementName: String): List = + mutableListOf().also { list -> + readElementConfigurable(element, rootElementName) { + val items = getChildren(elementName) + if (items.isNotEmpty()) { + items.mapNotNullTo(list) { (it.content.firstOrNull() as? Text)?.textTrim } + } else { + list += listOfNotNull((content.firstOrNull() as? Text)?.textTrim) + } + } + } + + private fun readArrayArguments(element: Element): Map> = mutableMapOf>().apply { + readElementConfigurable(element, ARRAY_ROOT_ELEMENTS_NAME) { + getChildren(ARRAY_ELEMENT_NAME).forEach { child -> + val name = child.getAttribute(NAME_ATTR_NAME)?.value ?: return@forEach + val array = readElementsList(child, ARGS_ATTR_NAME, ARG_ATTR_NAME).toTypedArray() + this@apply += name to array + } + } + } + + private fun parseInternalArgument(argument: String): InternalArgument? { + val parser = LanguageSettingsParser() + return parser.parseInternalArgument(argument, ArgumentParseErrors()) + } + } +} \ No newline at end of file diff --git a/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsSerializer.kt b/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsSerializer.kt new file mode 100644 index 00000000000..9f0b8fbd2c7 --- /dev/null +++ b/jps/jps-common/src/org/jetbrains/kotlin/arguments/CompilerArgumentsSerializer.kt @@ -0,0 +1,115 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.kotlin.arguments + +import org.jdom.Element +import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments +import org.jetbrains.kotlin.config.restoreNormalOrdering +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import java.io.File +import kotlin.reflect.KProperty1 + +interface CompilerArgumentsSerializer { + val arguments: T + fun serializeTo(element: Element): Element +} + +class CompilerArgumentsSerializerV5(override val arguments: T) : CompilerArgumentsSerializer { + + override fun serializeTo(element: Element): Element = Element(COMPILER_ARGUMENTS_ELEMENT_NAME).apply { + val newInstance = arguments::class.java.getConstructor().newInstance() + val flagArgumentsByName = CompilerArgumentsContentProspector.getFlagCompilerArgumentProperties(arguments::class) + .mapNotNull { prop -> + prop.safeAs>() + ?.takeIf { it.get(arguments) != it.get(newInstance) } + ?.get(arguments) + ?.let { prop.name to it } + }.toMap() + saveFlagArguments(this, flagArgumentsByName) + + val stringArgumentsByName = CompilerArgumentsContentProspector.getStringCompilerArgumentProperties(arguments::class) + .mapNotNull { prop -> + prop.safeAs>() + ?.takeIf { it.get(arguments) != it.get(newInstance) } + ?.get(arguments) + ?.let { prop.name to it } + }.toMap() + saveStringArguments(this, stringArgumentsByName) + + val arrayArgumentsByName = CompilerArgumentsContentProspector.getArrayCompilerArgumentProperties(arguments::class) + .mapNotNull { prop -> + prop.safeAs?>>() + ?.takeIf { it.get(arguments)?.contentEquals(it.get(newInstance)) != true } + ?.get(arguments) + ?.let { prop.name to it } + }.filterNot { it.second.isEmpty() } + .toMap() + saveArrayArguments(this, arrayArgumentsByName) + val freeArgs = CompilerArgumentsContentProspector.freeArgsProperty.get(arguments) + saveElementsList(this, FREE_ARGS_ROOT_ELEMENTS_NAME, FREE_ARGS_ELEMENT_NAME, freeArgs) + val internalArguments = CompilerArgumentsContentProspector.internalArgumentsProperty.get(arguments).map { it.stringRepresentation } + saveElementsList(this, INTERNAL_ARGS_ROOT_ELEMENTS_NAME, INTERNAL_ARGS_ELEMENT_NAME, internalArguments) + restoreNormalOrdering(arguments) + element.addContent(this) + } + + companion object { + private fun saveElementConfigurable(element: Element, rootElementName: String, configurable: Element.() -> Unit) { + element.addContent(Element(rootElementName).apply { configurable(this) }) + } + + private fun saveStringArguments(element: Element, argumentsByName: Map) { + if (argumentsByName.isEmpty()) return + saveElementConfigurable(element, STRING_ROOT_ELEMENTS_NAME) { + argumentsByName.entries.forEach { (name, arg) -> + Element(STRING_ELEMENT_NAME).also { + it.setAttribute(NAME_ATTR_NAME, name) + if (name == "classpath") { + saveElementsList(it, ARGS_ATTR_NAME, ARG_ATTR_NAME, arg.split(File.pathSeparator)) + } else { + it.setAttribute(ARG_ATTR_NAME, arg) + } + addContent(it) + } + } + } + } + + private fun saveFlagArguments(element: Element, argumentsByName: Map) { + if (argumentsByName.isEmpty()) return + saveElementConfigurable(element, FLAG_ROOT_ELEMENTS_NAME) { + argumentsByName.entries.forEach { (name, arg) -> + Element(FLAG_ELEMENT_NAME).also { + it.setAttribute(NAME_ATTR_NAME, name) + it.setAttribute(ARG_ATTR_NAME, arg.toString()) + addContent(it) + } + } + } + } + + private fun saveElementsList(element: Element, rootElementName: String, elementName: String, elementList: List) { + if (elementList.isEmpty()) return + saveElementConfigurable(element, rootElementName) { + val singleModule = elementList.singleOrNull() + if (singleModule != null) { + addContent(singleModule) + } else { + elementList.forEach { elementValue -> addContent(Element(elementName).also { it.addContent(elementValue) }) } + } + } + } + + private fun saveArrayArguments(element: Element, arrayArgumentsByName: Map>) { + if (arrayArgumentsByName.isEmpty()) return + saveElementConfigurable(element, ARRAY_ROOT_ELEMENTS_NAME) { + arrayArgumentsByName.entries.forEach { (name, arg) -> + Element(ARRAY_ELEMENT_NAME).also { + it.setAttribute(NAME_ATTR_NAME, name) + saveElementsList(it, ARGS_ATTR_NAME, ARG_ATTR_NAME, arg.toList()) + addContent(it) + } + } + } + } + } +} \ No newline at end of file diff --git a/jps/jps-common/src/org/jetbrains/kotlin/arguments/serializationUtils.kt b/jps/jps-common/src/org/jetbrains/kotlin/arguments/serializationUtils.kt new file mode 100644 index 00000000000..e0af786e454 --- /dev/null +++ b/jps/jps-common/src/org/jetbrains/kotlin/arguments/serializationUtils.kt @@ -0,0 +1,18 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.kotlin.arguments + +internal const val COMPILER_ARGUMENTS_ELEMENT_NAME = "compilerArguments" +internal const val FLAG_ROOT_ELEMENTS_NAME = "flagArguments" +internal const val FLAG_ELEMENT_NAME = "flagArg" +internal const val STRING_ROOT_ELEMENTS_NAME = "stringArguments" +internal const val STRING_ELEMENT_NAME = "stringArg" +internal const val ARRAY_ROOT_ELEMENTS_NAME = "arrayArguments" +internal const val ARRAY_ELEMENT_NAME = "arrayArg" +internal const val FREE_ARGS_ROOT_ELEMENTS_NAME = "freeArgs" +internal const val FREE_ARGS_ELEMENT_NAME = "freeArg" +internal const val INTERNAL_ARGS_ROOT_ELEMENTS_NAME = "internalArgs" +internal const val INTERNAL_ARGS_ELEMENT_NAME = "internalArg" + +internal const val NAME_ATTR_NAME = "name" +internal const val ARG_ATTR_NAME = "arg" +internal const val ARGS_ATTR_NAME = "args" diff --git a/jps/jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt b/jps/jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt index 6564d76c1aa..b340492b3f9 100644 --- a/jps/jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt +++ b/jps/jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt @@ -159,7 +159,7 @@ data class ExternalSystemNativeMainRunTask( class KotlinFacetSettings { companion object { // Increment this when making serialization-incompatible changes to configuration data - val CURRENT_VERSION = 4 + val CURRENT_VERSION = 5 val DEFAULT_VERSION = 0 } diff --git a/jps/jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt b/jps/jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt index 08ff7ea5927..c138a5c1c34 100644 --- a/jps/jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt +++ b/jps/jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt @@ -12,6 +12,9 @@ import com.intellij.util.xmlb.XmlSerializer import org.jdom.DataConversionException import org.jdom.Element import org.jdom.Text +import org.jetbrains.kotlin.arguments.COMPILER_ARGUMENTS_ELEMENT_NAME +import org.jetbrains.kotlin.arguments.CompilerArgumentsDeserializerV5 +import org.jetbrains.kotlin.arguments.CompilerArgumentsSerializerV5 import org.jetbrains.kotlin.cli.common.arguments.* import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.platform.* @@ -25,7 +28,7 @@ import java.lang.reflect.Modifier import kotlin.reflect.KClass import kotlin.reflect.full.superclasses -fun Element.getOption(name: String) = getChildren("option").firstOrNull { it.getAttribute("name").value == name } +fun Element.getOption(name: String) = getChildren("option").firstOrNull { name == it?.getAttribute("name")?.value } private fun Element.getOptionValue(name: String) = getOption(name)?.getAttribute("value")?.value @@ -124,7 +127,10 @@ fun Element.getFacetPlatformByConfigurationElement(): TargetPlatform { }.orDefault() // finally, fallback to the default platform } -private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings { +private fun readV2AndLaterConfig( + element: Element, + argumentReader: (Element, CommonToolArguments) -> Unit = { el, arg -> XmlSerializer.deserializeInto(arg, el) } +): KotlinFacetSettings { return KotlinFacetSettings().apply { element.getAttributeValue("useProjectSettings")?.let { useProjectSettings = it.toBoolean() } val targetPlatform = element.getFacetPlatformByConfigurationElement() @@ -164,12 +170,12 @@ private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings { compilerSettings = CompilerSettings() XmlSerializer.deserializeInto(compilerSettings!!, it) } - element.getChild("compilerArguments")?.let { + element.getChild(COMPILER_ARGUMENTS_ELEMENT_NAME)?.let { compilerArguments = targetPlatform.createArguments { freeArgs = mutableListOf() internalArguments = mutableListOf() } - XmlSerializer.deserializeInto(compilerArguments!!, it) + argumentReader(it, compilerArguments!!) compilerArguments!!.detectVersionAutoAdvance() } productionOutputPath = element.getChild("productionOutputPath")?.let { @@ -193,16 +199,12 @@ private fun readElementsList(element: Element, rootElementName: String, elementN return null } -private fun readV3Config(element: Element): KotlinFacetSettings { - return readV2AndLaterConfig(element) -} - private fun readV2Config(element: Element): KotlinFacetSettings { return readV2AndLaterConfig(element) } private fun readLatestConfig(element: Element): KotlinFacetSettings { - return readV2AndLaterConfig(element) + return readV2AndLaterConfig(element) { el, bean -> CompilerArgumentsDeserializerV5(bean).deserializeFrom(el) } } fun deserializeFacetSettings(element: Element): KotlinFacetSettings { @@ -213,8 +215,7 @@ fun deserializeFacetSettings(element: Element): KotlinFacetSettings { } ?: KotlinFacetSettings.DEFAULT_VERSION return when (version) { 1 -> readV1Config(element) - 2 -> readV2Config(element) - 3 -> readV3Config(element) + 2, 3, 4 -> readV2Config(element) KotlinFacetSettings.CURRENT_VERSION -> readLatestConfig(element) else -> return KotlinFacetSettings() // Reset facet configuration if versions don't match }.apply { this.version = version } @@ -278,7 +279,7 @@ private val Class<*>.normalOrdering // Replacing fields with delegated properties leads to unexpected reordering of entries in facet configuration XML // It happens due to XmlSerializer using different orderings for field- and method-based accessors // This code restores the original ordering -private fun Element.restoreNormalOrdering(bean: Any) { +internal fun Element.restoreNormalOrdering(bean: Any) { val normalOrdering = bean.javaClass.normalOrdering val elementsToReorder = this.getContent { it is Element && it.getAttribute("name")?.value in normalOrdering } elementsToReorder.sortedBy { normalOrdering[it.getAttribute("name")?.value!!] } @@ -293,7 +294,7 @@ private fun buildChildElement(element: Element, tag: String, bean: Any, filter: } } -private fun KotlinFacetSettings.writeLatestConfig(element: Element) { +private fun KotlinFacetSettings.writeConfig(element: Element) { val filter = SkipDefaultsSerializationFilter() // TODO: Introduce new version of facet serialization. See https://youtrack.jetbrains.com/issue/KT-38235 @@ -362,9 +363,20 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) { it.convertPathsToSystemIndependent() buildChildElement(element, "compilerSettings", it, filter) } +} + +private fun KotlinFacetSettings.writeV2toV4Config(element: Element) = writeConfig(element).apply { compilerArguments?.let { copyBean(it) }?.let { it.convertPathsToSystemIndependent() - val compilerArgumentsXml = buildChildElement(element, "compilerArguments", it, filter) + val compilerArgumentsXml = buildChildElement(element, "compilerArguments", it, SkipDefaultsSerializationFilter()) + compilerArgumentsXml.dropVersionsIfNecessary(it) + } +} + +private fun KotlinFacetSettings.writeLatestConfig(element: Element) = writeConfig(element).apply { + compilerArguments?.let { copyBean(it) }?.let { + it.convertPathsToSystemIndependent() + val compilerArgumentsXml = CompilerArgumentsSerializerV5(it).serializeTo(element) compilerArgumentsXml.dropVersionsIfNecessary(it) } } @@ -400,15 +412,18 @@ fun Element.dropVersionsIfNecessary(settings: CommonCompilerArguments) { } } -fun KotlinFacetSettings.serializeFacetSettings(element: Element) { - val versionToWrite = when (version) { - 2, 3 -> version - else -> KotlinFacetSettings.CURRENT_VERSION +fun KotlinFacetSettings.serializeFacetSettings(element: Element) = when (version) { + 2, 3, 4 -> { + element.setAttribute("version", version.toString()) + writeV2toV4Config(element) + } + else -> { + element.setAttribute("version", KotlinFacetSettings.CURRENT_VERSION.toString()) + writeLatestConfig(element) } - element.setAttribute("version", versionToWrite.toString()) - writeLatestConfig(element) } + private fun TargetPlatform.serializeComponentPlatforms(): String { val componentPlatforms = componentPlatforms val componentPlatformNames = componentPlatforms.mapTo(ArrayList()) { it.serializeToString() } diff --git a/jps/jps-common/test/CompilerArgumentsContentProspectorTest.kt b/jps/jps-common/test/CompilerArgumentsContentProspectorTest.kt new file mode 100644 index 00000000000..4e51dbbaeff --- /dev/null +++ b/jps/jps-common/test/CompilerArgumentsContentProspectorTest.kt @@ -0,0 +1,263 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.kotlin.arguments + +import org.jetbrains.kotlin.cli.common.arguments.* +import org.junit.Test + + +class CompilerArgumentsContentProspectorTest { + + @Test + fun testJVMArgumentsContent() { + val flagProperties = CompilerArgumentsContentProspector.getFlagCompilerArgumentProperties(K2JVMCompilerArguments::class) + val stringProperties = CompilerArgumentsContentProspector.getStringCompilerArgumentProperties(K2JVMCompilerArguments::class) + val arrayProperties = CompilerArgumentsContentProspector.getArrayCompilerArgumentProperties(K2JVMCompilerArguments::class) + + assertContentEquals(flagProperties, k2JVMCompilerArgumentsFlagProperties) { sortedBy { it.name } } + assertContentEquals(stringProperties, k2JVMCompilerArgumentsStringProperties) { sortedBy { it.name } } + assertContentEquals(arrayProperties, k2JVMCompilerArgumentsArrayArgumentProperties) { sortedBy { it.name } } + } + + @Test + fun testMetadataArgumentsContent() { + val flagProperties = CompilerArgumentsContentProspector.getFlagCompilerArgumentProperties(K2MetadataCompilerArguments::class) + val stringProperties = CompilerArgumentsContentProspector.getStringCompilerArgumentProperties(K2MetadataCompilerArguments::class) + val arrayProperties = CompilerArgumentsContentProspector.getArrayCompilerArgumentProperties(K2MetadataCompilerArguments::class) + + assertContentEquals(flagProperties, k2MetadataCompilerArgumentsFlagProperties) { sortedBy { it.name } } + assertContentEquals(stringProperties, k2MetadataCompilerArgumentsStringProperties) { sortedBy { it.name } } + assertContentEquals(arrayProperties, k2MetadataCompilerArgumentsArrayProperties) { sortedBy { it.name } } + } + + @Test + fun testJsArgumentsContent() { + val flagProperties = CompilerArgumentsContentProspector.getFlagCompilerArgumentProperties(K2JSCompilerArguments::class) + val stringProperties = CompilerArgumentsContentProspector.getStringCompilerArgumentProperties(K2JSCompilerArguments::class) + val arrayProperties = CompilerArgumentsContentProspector.getArrayCompilerArgumentProperties(K2JSCompilerArguments::class) + + assertContentEquals(flagProperties, k2JSCompilerArgumentsFlagProperties) { sortedBy { it.name } } + assertContentEquals(stringProperties, k2JSCompilerArgumentsStringProperties) { sortedBy { it.name } } + assert(arrayProperties.isEmpty()) { "Expected empty arrayProperties, but actual ${arrayProperties.joinToString { it.name }}" } + } + + @Test + fun testJsDceArgumentsContent() { + val flagProperties = CompilerArgumentsContentProspector.getFlagCompilerArgumentProperties(K2JSDceArguments::class) + val stringProperties = CompilerArgumentsContentProspector.getStringCompilerArgumentProperties(K2JSDceArguments::class) + val arrayProperties = CompilerArgumentsContentProspector.getArrayCompilerArgumentProperties(K2JSDceArguments::class) + + assertContentEquals(flagProperties, k2JSDceCompilerArgumentsFlagProperties) { sortedBy { it.name } } + assertContentEquals(stringProperties, k2JSDceCompilerArgumentsStringProperties) { sortedBy { it.name } } + assertContentEquals(arrayProperties, k2JSDceCompilerArgumentsArrayProperties) { sortedBy { it.name } } + } + + companion object { + + private val commonToolArgumentsFlagProperties = listOf( + CommonToolArguments::allWarningsAsErrors, + CommonToolArguments::extraHelp, + CommonToolArguments::help, + CommonToolArguments::suppressWarnings, + CommonToolArguments::verbose, + CommonToolArguments::version, + ) + + private val commonCompilerArgumentsFlagProperties = commonToolArgumentsFlagProperties + listOf( + CommonCompilerArguments::allowKotlinPackage, + CommonCompilerArguments::progressiveMode, + CommonCompilerArguments::script, + CommonCompilerArguments::noInline, + CommonCompilerArguments::skipMetadataVersionCheck, + CommonCompilerArguments::skipPrereleaseCheck, + CommonCompilerArguments::allowKotlinPackage, + CommonCompilerArguments::reportOutputFiles, + CommonCompilerArguments::multiPlatform, + CommonCompilerArguments::noCheckActual, + CommonCompilerArguments::newInference, + CommonCompilerArguments::inlineClasses, + CommonCompilerArguments::polymorphicSignature, + CommonCompilerArguments::legacySmartCastAfterTry, + CommonCompilerArguments::effectSystem, + CommonCompilerArguments::readDeserializedContracts, + CommonCompilerArguments::properIeee754Comparisons, + CommonCompilerArguments::reportPerf, + CommonCompilerArguments::allowResultReturnType, + CommonCompilerArguments::listPhases, + CommonCompilerArguments::profilePhases, + CommonCompilerArguments::checkPhaseConditions, + CommonCompilerArguments::checkStickyPhaseConditions, + CommonCompilerArguments::useFir, + CommonCompilerArguments::useFirExtendedCheckers, + CommonCompilerArguments::disableUltraLightClasses, + CommonCompilerArguments::useMixedNamedArguments, + CommonCompilerArguments::expectActualLinker, + CommonCompilerArguments::disableDefaultScriptingPlugin, + CommonCompilerArguments::inferenceCompatibility + ) + + private val commonCompilerArgumentsStringProperties = listOf( + CommonCompilerArguments::intellijPluginRoot, + CommonCompilerArguments::dumpPerf, + CommonCompilerArguments::metadataVersion, + CommonCompilerArguments::dumpDirectory, + CommonCompilerArguments::dumpOnlyFqName, + CommonCompilerArguments::explicitApi + ) + private val commonCompilerArgumentsArrayArgumentProperties = listOf( + CommonCompilerArguments::pluginOptions, + CommonCompilerArguments::pluginClasspaths, + CommonCompilerArguments::experimental, + CommonCompilerArguments::useExperimental, + CommonCompilerArguments::optIn, + CommonCompilerArguments::commonSources, + CommonCompilerArguments::disablePhases, + CommonCompilerArguments::verbosePhases, + CommonCompilerArguments::phasesToDumpBefore, + CommonCompilerArguments::phasesToDumpAfter, + CommonCompilerArguments::phasesToDump, + CommonCompilerArguments::namesExcludedFromDumping, + CommonCompilerArguments::phasesToValidateBefore, + CommonCompilerArguments::phasesToValidateAfter, + CommonCompilerArguments::phasesToValidate, + ) + + private val k2JVMCompilerArgumentsFlagProperties = commonCompilerArgumentsFlagProperties + listOf( + K2JVMCompilerArguments::includeRuntime, + K2JVMCompilerArguments::noJdk, + K2JVMCompilerArguments::noStdlib, + K2JVMCompilerArguments::noReflect, + K2JVMCompilerArguments::javaParameters, + K2JVMCompilerArguments::useIR, + K2JVMCompilerArguments::useOldBackend, + K2JVMCompilerArguments::allowUnstableDependencies, + K2JVMCompilerArguments::doNotClearBindingContext, + K2JVMCompilerArguments::noCallAssertions, + K2JVMCompilerArguments::noReceiverAssertions, + K2JVMCompilerArguments::noParamAssertions, + K2JVMCompilerArguments::strictJavaNullabilityAssertions, + K2JVMCompilerArguments::noOptimize, + K2JVMCompilerArguments::inheritMultifileParts, + K2JVMCompilerArguments::useTypeTable, + K2JVMCompilerArguments::skipRuntimeVersionCheck, + K2JVMCompilerArguments::useOldClassFilesReading, + K2JVMCompilerArguments::singleModule, + K2JVMCompilerArguments::suppressMissingBuiltinsError, + K2JVMCompilerArguments::useJavac, + K2JVMCompilerArguments::compileJava, + K2JVMCompilerArguments::noExceptionOnExplicitEqualsForBoxedNull, + K2JVMCompilerArguments::disableStandardScript, + K2JVMCompilerArguments::strictMetadataVersionSemantics, + K2JVMCompilerArguments::sanitizeParentheses, + K2JVMCompilerArguments::allowNoSourceFiles, + K2JVMCompilerArguments::emitJvmTypeAnnotations, + K2JVMCompilerArguments::noOptimizedCallableReferences, + K2JVMCompilerArguments::noKotlinNothingValueException, + K2JVMCompilerArguments::noResetJarTimestamps, + K2JVMCompilerArguments::noUnifiedNullChecks, + K2JVMCompilerArguments::useOldSpilledVarTypeAnalysis, + K2JVMCompilerArguments::useOldInlineClassesManglingScheme, + K2JVMCompilerArguments::enableJvmPreview, + ) + + private val k2JVMCompilerArgumentsStringProperties = commonCompilerArgumentsStringProperties + listOf( + K2JVMCompilerArguments::destination, + K2JVMCompilerArguments::classpath, + K2JVMCompilerArguments::jdkHome, + K2JVMCompilerArguments::expression, + K2JVMCompilerArguments::moduleName, + K2JVMCompilerArguments::jvmTarget, + K2JVMCompilerArguments::abiStability, + K2JVMCompilerArguments::javaModulePath, + K2JVMCompilerArguments::constructorCallNormalizationMode, + K2JVMCompilerArguments::assertionsMode, + K2JVMCompilerArguments::buildFile, + K2JVMCompilerArguments::declarationsOutputPath, + K2JVMCompilerArguments::javaPackagePrefix, + K2JVMCompilerArguments::supportCompatqualCheckerFrameworkAnnotations, + K2JVMCompilerArguments::jspecifyAnnotations, + K2JVMCompilerArguments::jvmDefault, + K2JVMCompilerArguments::defaultScriptExtension, + K2JVMCompilerArguments::stringConcat, + K2JVMCompilerArguments::klibLibraries, + K2JVMCompilerArguments::profileCompilerCommand, + K2JVMCompilerArguments::repeatCompileModules + ) + + private val k2JVMCompilerArgumentsArrayArgumentProperties = commonCompilerArgumentsArrayArgumentProperties + listOf( + K2JVMCompilerArguments::scriptTemplates, + K2JVMCompilerArguments::additionalJavaModules, + K2JVMCompilerArguments::scriptResolverEnvironment, + K2JVMCompilerArguments::javacArguments, + K2JVMCompilerArguments::javaSourceRoots, + K2JVMCompilerArguments::jsr305, + K2JVMCompilerArguments::friendPaths + ) + + private val k2MetadataCompilerArgumentsFlagProperties = commonCompilerArgumentsFlagProperties + listOf( + K2MetadataCompilerArguments::enabledInJps + ) + private val k2MetadataCompilerArgumentsStringProperties = commonCompilerArgumentsStringProperties + listOf( + K2MetadataCompilerArguments::destination, + K2MetadataCompilerArguments::classpath, + K2MetadataCompilerArguments::moduleName + ) + private val k2MetadataCompilerArgumentsArrayProperties = commonCompilerArgumentsArrayArgumentProperties + listOf( + K2MetadataCompilerArguments::friendPaths, + K2MetadataCompilerArguments::refinesPaths + ) + private val k2JSCompilerArgumentsFlagProperties = commonCompilerArgumentsFlagProperties + listOf( + K2JSCompilerArguments::noStdlib, + K2JSCompilerArguments::sourceMap, + K2JSCompilerArguments::metaInfo, + K2JSCompilerArguments::irProduceKlibDir, + K2JSCompilerArguments::irProduceKlibFile, + K2JSCompilerArguments::irProduceJs, + K2JSCompilerArguments::irDce, + K2JSCompilerArguments::irDceDriven, + K2JSCompilerArguments::irDcePrintReachabilityInfo, + K2JSCompilerArguments::irPropertyLazyInitialization, + K2JSCompilerArguments::irOnly, + K2JSCompilerArguments::irPerModule, + K2JSCompilerArguments::generateDts, + K2JSCompilerArguments::typedArrays, + K2JSCompilerArguments::friendModulesDisabled, + K2JSCompilerArguments::metadataOnly, + K2JSCompilerArguments::enableJsScripting, + K2JSCompilerArguments::fakeOverrideValidator, + K2JSCompilerArguments::wasm + ) + private val k2JSCompilerArgumentsStringProperties = commonCompilerArgumentsStringProperties + listOf( + K2JSCompilerArguments::outputFile, + K2JSCompilerArguments::libraries, + K2JSCompilerArguments::sourceMapPrefix, + K2JSCompilerArguments::sourceMapBaseDirs, + K2JSCompilerArguments::sourceMapEmbedSources, + K2JSCompilerArguments::target, + K2JSCompilerArguments::moduleKind, + K2JSCompilerArguments::main, + K2JSCompilerArguments::outputPrefix, + K2JSCompilerArguments::outputPostfix, + K2JSCompilerArguments::irModuleName, + K2JSCompilerArguments::includes, + K2JSCompilerArguments::friendModules, + K2JSCompilerArguments::errorTolerancePolicy, + ) + + private val k2JSDceCompilerArgumentsFlagProperties = commonToolArgumentsFlagProperties + listOf( + K2JSDceArguments::devMode, + K2JSDceArguments::printReachabilityInfo, + ) + private val k2JSDceCompilerArgumentsStringProperties = listOf( + K2JSDceArguments::outputDirectory, + K2JSDceArguments::devModeOverwritingStrategy, + ) + private val k2JSDceCompilerArgumentsArrayProperties = listOf( + K2JSDceArguments::declarationsToKeep + ) + + private fun assertContentEquals(expect: Iterable, actual: Iterable, preprocessor: Iterable.() -> Iterable) { + (expect.count() == actual.count()) && preprocessor(expect).zip(preprocessor(actual)).all { it.first == it.second } + } + + } +} \ No newline at end of file diff --git a/jps/jps-common/test/CompilerArgumentsSerializationTest.kt b/jps/jps-common/test/CompilerArgumentsSerializationTest.kt new file mode 100644 index 00000000000..610f49256a6 --- /dev/null +++ b/jps/jps-common/test/CompilerArgumentsSerializationTest.kt @@ -0,0 +1,169 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.kotlin.arguments + +import org.jdom.Element +import org.jetbrains.kotlin.cli.common.arguments.* +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.junit.Test +import kotlin.random.Random +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.memberProperties + +class CompilerArgumentsSerializationTest { + + @Test + fun testDummyJVM() { + doSerializeDeserializeAndCompareTest() + } + + @Test + fun testRandomFlagArgumentsJVM() { + doRandomFlagArgumentsTest() + } + + @Test + fun testRandomStringArgumentsJVM() { + doRandomStringArgumentsTest() + } + + @Test + fun testLongClasspathArgumentJVM() { + doSerializeDeserializeAndCompareTest { + classpath = generateSequence { generateRandomString(50) }.take(10).toList().joinToString(File.pathSeparator) + } + } + + + @Test + fun testRandomArrayArgumentsJVM() { + doRandomArrayArgumentsTest() + } + + @Test + fun testDummyJs() { + doSerializeDeserializeAndCompareTest() + } + + @Test + fun testRandomFlagArgumentsJS() { + doRandomFlagArgumentsTest() + } + + @Test + fun testRandomStringArgumentsJS() { + doRandomStringArgumentsTest() + } + + @Test + fun testRandomArrayArgumentsJS() { + doRandomArrayArgumentsTest() + } + + @Test + fun testDummyMetadata() { + doSerializeDeserializeAndCompareTest() + } + + @Test + fun testRandomFlagArgumentsMetadata() { + doRandomFlagArgumentsTest() + } + + @Test + fun testRandomStringArgumentsMetadata() { + doRandomStringArgumentsTest() + } + + @Test + fun testLongClasspathArgumentMetadata() { + doSerializeDeserializeAndCompareTest { + classpath = generateSequence { generateRandomString(50) }.take(10).toList().joinToString(File.pathSeparator) + } + } + + @Test + fun testRandomArrayArgumentsMetadata() { + doRandomArrayArgumentsTest() + } + + + @Test + fun testDummyJsDce() { + doSerializeDeserializeAndCompareTest() + } + + @Test + fun testRandomFlagArgumentsJSDce() { + doRandomFlagArgumentsTest() + } + + @Test + fun testRandomStringArgumentsJSDce() { + doRandomStringArgumentsTest() + } + + @Test + fun testRandomArrayArgumentsJSDce() { + doRandomArrayArgumentsTest() + } + + + private inline fun doSerializeDeserializeAndCompareTest(configure: T.() -> Unit = {}) { + val oldInstance = T::class.java.getConstructor().newInstance().apply(configure) + val serializer = CompilerArgumentsSerializerV5(oldInstance) + val mockFacetElement = Element("ROOT") + val element = serializer.serializeTo(mockFacetElement) + val newInstance = T::class.java.getConstructor().newInstance() + val deserializer = CompilerArgumentsDeserializerV5(newInstance) + deserializer.deserializeFrom(element) + T::class.memberProperties.mapNotNull { it.safeAs>() }.forEach { + assert(it.get(oldInstance) == it.get(newInstance)) { + "Property ${it.name} has different values before (${it.get(oldInstance)}) and after (${it.get(newInstance)}) serialization" + } + } + } + + private inline fun doRandomFlagArgumentsTest() { + val flagProperties = CompilerArgumentsContentProspector.getFlagCompilerArgumentProperties(T::class) + val randomFlags = generateSequence { Random.nextBoolean() }.take(flagProperties.size).toList() + doSerializeDeserializeAndCompareTest { + flagProperties.zip(randomFlags).forEach { + it.first.cast>().set(this, it.second) + } + } + } + + private inline fun doRandomStringArgumentsTest() { + val stringProperties = CompilerArgumentsContentProspector.getStringCompilerArgumentProperties(T::class) + val randomStrings = generateSequence { generateRandomString(Random.nextInt(20)) }.take(stringProperties.size).toList() + doSerializeDeserializeAndCompareTest { + stringProperties.zip(randomStrings).forEach { + it.first.cast>().set(this, it.second) + } + } + } + + private inline fun doRandomArrayArgumentsTest() { + val arrayProperties = CompilerArgumentsContentProspector.getArrayCompilerArgumentProperties(T::class) + val randomArrays = generateSequence { + generateSequence { generateRandomString(Random.nextInt(20)) }.take(Random.nextInt(10)).toList().toTypedArray() + }.take(arrayProperties.size).toList() + doSerializeDeserializeAndCompareTest { + arrayProperties.zip(randomArrays).forEach { + it.first.cast?>>().set(this, it.second) + } + } + } + + companion object { + private val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') + + private fun generateRandomString(length: Int) = generateSequence { Random.nextInt(0, charPool.size) } + .take(length) + .map(charPool::get) + .joinToString("") + } +} \ No newline at end of file