MT tests: rewrite model dump and reading to javax.xml
note that format of the argument part is changed,
but reading of the old format is supported
#KT-81046 fixed
diff --git a/compiler/cli/build.gradle.kts b/compiler/cli/build.gradle.kts
index 13ce77d..6a499ea 100644
--- a/compiler/cli/build.gradle.kts
+++ b/compiler/cli/build.gradle.kts
@@ -30,6 +30,7 @@
api(project(":compiler:fir:fir-serialization"))
api(project(":compiler:ir.inline"))
api(project(":kotlin-util-io"))
+ implementation(project(":kotlin-build-common"))
compileOnly(toolsJarApi())
compileOnly(intellijCore())
diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt
index ee752a1..f91ab58 100644
--- a/compiler/cli/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt
+++ b/compiler/cli/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt
@@ -9,13 +9,6 @@
import com.intellij.openapi.Disposable
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
-import com.intellij.util.xmlb.SkipDefaultsSerializationFilter
-import com.intellij.util.xmlb.XmlSerializer
-import org.jdom.Attribute
-import org.jdom.Document
-import org.jdom.Element
-import org.jdom.output.Format
-import org.jdom.output.XMLOutputter
import org.jetbrains.kotlin.KtPsiSourceFile
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.cli.common.*
@@ -38,6 +31,7 @@
import org.jetbrains.kotlin.cli.pipeline.jvm.JvmFrontendPipelinePhase.createEnvironmentAndSources
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector
import org.jetbrains.kotlin.fir.DependencyListForCliModule
@@ -55,6 +49,8 @@
import org.jetbrains.kotlin.util.PhaseType
import org.jetbrains.kotlin.utils.fileUtils.descendantRelativeTo
import java.io.File
+import javax.xml.stream.XMLOutputFactory
+import javax.xml.stream.XMLStreamWriter
object JvmFrontendPipelinePhase : PipelinePhase<ConfigurationPipelineArtifact, JvmFrontendPipelineArtifact>(
name = "JvmFrontendPipelinePhase",
@@ -66,63 +62,6 @@
configuration: CompilerConfiguration,
arguments: CommonCompilerArguments,
) {
- val modules = Element("modules").apply {
- // Just write out all compiler arguments as is
- addContent(
- Element("compilerArguments").apply {
- val skipDefaultsFilter = SkipDefaultsSerializationFilter()
- val element = XmlSerializer.serialize(arguments, skipDefaultsFilter)
- addContent(element)
- }
- )
- for (module in chunk) {
- addContent(Element("module").apply {
- attributes.add(
- Attribute("timestamp", System.currentTimeMillis().toString())
- )
-
- attributes.add(
- Attribute("name", module.getModuleName())
- )
- attributes.add(
- Attribute("type", module.getModuleType())
- )
- attributes.add(
- Attribute("outputDir", module.getOutputDirectory())
- )
-
- for (friendDir in module.getFriendPaths()) {
- addContent(Element("friendDir").setAttribute("path", friendDir))
- }
- for (source in module.getSourceFiles()) {
- addContent(Element("sources").setAttribute("path", source))
- }
- for (javaSourceRoots in module.getJavaSourceRoots()) {
- addContent(
- Element("javaSourceRoots").apply {
- setAttribute("path", javaSourceRoots.path)
- javaSourceRoots.packagePrefix?.let { setAttribute("packagePrefix", it) }
- }
- )
- }
- for (classpath in configuration.get(CONTENT_ROOTS).orEmpty()) {
- if (classpath is JvmClasspathRoot) {
- addContent(Element("classpath").setAttribute("path", classpath.file.absolutePath))
- } else if (classpath is JvmModulePathRoot) {
- addContent(Element("modulepath").setAttribute("path", classpath.file.absolutePath))
- }
- }
- for (commonSources in module.getCommonSourceFiles()) {
- addContent(Element("commonSources").setAttribute("path", commonSources))
- }
- module.modularJdkRoot?.let {
- addContent(Element("modularJdkRoot").setAttribute("path", module.modularJdkRoot))
- }
- })
- }
- }
- val document = Document(modules)
- val outputter = XMLOutputter(Format.getPrettyFormat())
val dirFile = File(dir)
if (!dirFile.exists()) {
dirFile.mkdirs()
@@ -139,10 +78,78 @@
outputFile = file()
counter++
} while (outputFile.exists())
- outputFile.bufferedWriter().use {
- outputter.output(document, it)
- }
+ // Write XML using StAX
+ outputFile.bufferedWriter().use { writer ->
+ val xmlFactory = XMLOutputFactory.newInstance()
+ with(xmlFactory.createXMLStreamWriter(writer)) {
+ writeStartDocument("UTF-8", "1.0")
+ val depth = PrettyPrintDepth(0)
+
+ // <modules>
+ start("modules", depth)
+
+ // compilerArguments
+ start("compilerArguments", depth)
+ for (arg in ArgumentUtils.convertArgumentsToStringList(arguments)) {
+ empty("arg", depth)
+ writeAttribute("value", arg)
+ }
+ end(depth) // compilerArguments
+
+ // modules
+ for (module in chunk) {
+ start("module", depth)
+ writeAttribute("timestamp", System.currentTimeMillis().toString())
+ writeAttribute("name", module.getModuleName())
+ writeAttribute("type", module.getModuleType())
+ writeAttribute("outputDir", module.getOutputDirectory())
+
+ for (friendDir in module.getFriendPaths()) {
+ empty("friendDir", depth)
+ writeAttribute("path", friendDir)
+ }
+ for (source in module.getSourceFiles()) {
+ empty("sources", depth)
+ writeAttribute("path", source)
+ }
+ for (javaSourceRoots in module.getJavaSourceRoots()) {
+ start("javaSourceRoots", depth)
+ writeAttribute("path", javaSourceRoots.path)
+ javaSourceRoots.packagePrefix?.let { writeAttribute("packagePrefix", it) }
+ end(depth)
+ }
+ for (classpath in configuration.get(CONTENT_ROOTS).orEmpty()) {
+ when (classpath) {
+ is JvmClasspathRoot -> {
+ empty("classpath", depth)
+ writeAttribute("path", classpath.file.absolutePath)
+ }
+ is JvmModulePathRoot -> {
+ empty("modulepath", depth)
+ writeAttribute("path", classpath.file.absolutePath)
+ }
+ }
+ }
+ for (commonSources in module.getCommonSourceFiles()) {
+ empty("commonSources", depth)
+ writeAttribute("path", commonSources)
+ }
+ module.modularJdkRoot?.let {
+ empty("modularJdkRoot", depth)
+ writeAttribute("path", it)
+ }
+
+ end(depth) // module
+ }
+
+ end(depth) // modules
+ writeCharacters("\n")
+ writeEndDocument()
+ flush()
+ close()
+ }
+ }
}
override fun executePhase(input: ConfigurationPipelineArtifact): JvmFrontendPipelineArtifact? {
@@ -473,3 +480,29 @@
)
}
}
+
+
+// Pretty-printing helpers for StAX writer
+private data class PrettyPrintDepth(var value: Int)
+
+private fun XMLStreamWriter.indent(depth: PrettyPrintDepth) {
+ writeCharacters("\n")
+ if (depth.value > 0) writeCharacters(" ".repeat(depth.value))
+}
+
+private fun XMLStreamWriter.start(name: String, depth: PrettyPrintDepth) {
+ indent(depth)
+ writeStartElement(name)
+ depth.value++
+}
+
+private fun XMLStreamWriter.end(depth: PrettyPrintDepth) {
+ depth.value--
+ indent(depth)
+ writeEndElement()
+}
+
+private fun XMLStreamWriter.empty(name: String, depth: PrettyPrintDepth) {
+ indent(depth)
+ writeEmptyElement(name)
+}
diff --git a/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/AbstractModularizedTest.kt b/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/AbstractModularizedTest.kt
index 0b107ea..c36c197 100644
--- a/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/AbstractModularizedTest.kt
+++ b/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/AbstractModularizedTest.kt
@@ -5,11 +5,9 @@
package org.jetbrains.kotlin.fir
-import com.intellij.openapi.util.JDOMUtil
-import com.intellij.util.xmlb.XmlSerializer
-import org.jdom.Element
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
+import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
import org.jetbrains.kotlin.test.kotlinPathsForDistDirectoryForTests
@@ -20,6 +18,12 @@
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
+import javax.xml.stream.XMLInputFactory
+import javax.xml.stream.XMLStreamConstants
+import javax.xml.stream.XMLStreamReader
+import kotlin.reflect.KClass
+import kotlin.reflect.KMutableProperty1
+import kotlin.reflect.full.memberProperties
data class ModuleData(
val name: String,
@@ -180,73 +184,277 @@
}
internal fun loadModuleDumpFile(file: File): List<ModuleData> {
- val rootElement = JDOMUtil.load(file)
- val modules = rootElement.getChildren("module")
- val arguments = rootElement.getChild("compilerArguments")?.let { loadCompilerArguments(it) }
- return modules.map { node -> loadModule(node).also { it.arguments = arguments } }
+ val modules = mutableListOf<ModuleData>()
+ var arguments: CommonCompilerArguments? = null
+
+ val xmlFactory = XMLInputFactory.newInstance()
+ file.inputStream().use { input ->
+ val xr = xmlFactory.createXMLStreamReader(input)
+ while (xr.hasNext()) {
+ when (xr.next()) {
+ XMLStreamConstants.START_ELEMENT -> when (xr.localName) {
+ "compilerArguments" -> {
+ arguments = readCompilerArguments(xr)
+ // Assign to already parsed modules as well
+ modules.forEach { it.arguments = arguments }
+ }
+ "module" -> {
+ val m = readModule(xr)
+ m.arguments = arguments
+ modules += m
+ }
+ else -> {}
+ }
+ else -> {}
+ }
+ }
+ xr.close()
+ }
+ return modules
}
-private fun loadModule(moduleElement: Element): ModuleData {
- val outputDir = moduleElement.getAttribute("outputDir").value
- val moduleName = moduleElement.getAttribute("name").value
+private fun readModule(xr: XMLStreamReader): ModuleData {
+ // reader is positioned at START_ELEMENT <module>
+ val outputDir = xr.getAttributeValue(null, "outputDir") ?: ""
+ val moduleName = xr.getAttributeValue(null, "name") ?: ""
val moduleNameQualifier = outputDir.substringAfterLast("/")
+ val timestamp = xr.getAttributeValue(null, "timestamp")?.toLongOrNull() ?: 0L
+ val jdkHome = xr.getAttributeValue(null, "jdkHome")
+
val javaSourceRoots = mutableListOf<JavaSourceRootData<String>>()
val classpath = mutableListOf<String>()
val sources = mutableListOf<String>()
val friendDirs = mutableListOf<String>()
val optInAnnotations = mutableListOf<String>()
- val timestamp = moduleElement.getAttribute("timestamp")?.longValue ?: 0
- val jdkHome = moduleElement.getAttribute("jdkHome")?.value
var modularJdkRoot: String? = null
var isCommon = false
- for (item in moduleElement.children) {
- when (item.name) {
- "classpath" -> {
- val path = item.getAttribute("path").value
- if (path != outputDir) {
- classpath += path
+ while (xr.hasNext()) {
+ when (xr.next()) {
+ XMLStreamConstants.START_ELEMENT -> when (xr.localName) {
+ "classpath" -> {
+ val path = xr.getAttributeValue(null, "path")
+ if (path != null && path != outputDir) classpath += path
+ skipElement(xr)
+ }
+ "friendDir" -> {
+ xr.getAttributeValue(null, "path")?.let { friendDirs += it }
+ skipElement(xr)
+ }
+ "javaSourceRoots" -> {
+ val path = xr.getAttributeValue(null, "path")
+ val pkg = xr.getAttributeValue(null, "packagePrefix")
+ if (path != null) javaSourceRoots += JavaSourceRootData(path, pkg)
+ skipElement(xr)
+ }
+ "sources" -> {
+ xr.getAttributeValue(null, "path")?.let { sources += it }
+ skipElement(xr)
+ }
+ "commonSources" -> {
+ isCommon = true
+ skipElement(xr)
+ }
+ "modularJdkRoot" -> {
+ modularJdkRoot = xr.getAttributeValue(null, "path")
+ skipElement(xr)
+ }
+ "useOptIn" -> {
+ xr.getAttributeValue(null, "annotation")?.let { optInAnnotations += it }
+ skipElement(xr)
+ }
+ else -> {
+ // Skip any unknown children fully
+ skipElement(xr)
}
}
- "friendDir" -> {
- val path = item.getAttribute("path").value
- friendDirs += path
+ XMLStreamConstants.END_ELEMENT -> if (xr.localName == "module") {
+ return ModuleData(
+ moduleName,
+ timestamp,
+ outputDir,
+ moduleNameQualifier,
+ classpath,
+ sources,
+ javaSourceRoots,
+ friendDirs,
+ optInAnnotations,
+ modularJdkRoot,
+ jdkHome,
+ isCommon,
+ )
}
- "javaSourceRoots" -> {
- javaSourceRoots +=
- JavaSourceRootData(
- item.getAttribute("path").value,
- item.getAttribute("packagePrefix")?.value,
- )
- }
- "sources" -> sources += item.getAttribute("path").value
- "commonSources" -> isCommon = true
- "modularJdkRoot" -> modularJdkRoot = item.getAttribute("path").value
- "useOptIn" -> optInAnnotations += item.getAttribute("annotation").value
}
}
-
- return ModuleData(
- moduleName,
- timestamp,
- outputDir,
- moduleNameQualifier,
- classpath,
- sources,
- javaSourceRoots,
- friendDirs,
- optInAnnotations,
- modularJdkRoot,
- jdkHome,
- isCommon,
- )
+ error("Unexpected end of XML while reading <module>")
}
-private fun loadCompilerArguments(argumentsRoot: Element): CommonCompilerArguments? {
- val element = argumentsRoot.children.singleOrNull() ?: return null
- return when (element.name) {
- "K2JVMCompilerArguments" -> K2JVMCompilerArguments().also { XmlSerializer.deserializeInto(it, element) }
- else -> null
+private fun readCompilerArguments(xr: XMLStreamReader): CommonCompilerArguments {
+ // reader is positioned at START_ELEMENT <compilerArguments>
+ val argList = mutableListOf<String>()
+ var oldFormatArgs: CommonCompilerArguments? = null
+ while (xr.hasNext()) {
+ when (xr.next()) {
+ XMLStreamConstants.START_ELEMENT -> {
+ when (xr.localName) {
+ "arg" -> {
+ xr.getAttributeValue(null, "value")?.let { argList += it }
+ skipElement(xr)
+ }
+ // Old variant that was serialized with com.intellij.util.xmlb + org.jdom
+ "K2JVMCompilerArguments" -> {
+ oldFormatArgs = readCompilerArgumentsOldVariant(xr)
+ }
+ else -> {
+ // Unknown child, skip it entirely to keep parser in sync
+ skipElement(xr)
+ }
+ }
+ }
+ XMLStreamConstants.END_ELEMENT -> if (xr.localName == "compilerArguments") {
+ return oldFormatArgs ?: parseCommandLineArguments<K2JVMCompilerArguments>(argList)
+ }
+ }
+ }
+ error("Unexpected end of XML while reading <compilerArguments>")
+}
+
+// Old XML model variant deserializer using StAX and reflection. (serialized with com.intellij.util.xmlb + org.jdom)
+// TODO: drop when no longer needed (KT-80860)
+private fun readCompilerArgumentsOldVariant(xr: XMLStreamReader): CommonCompilerArguments {
+ // reader is positioned at START_ELEMENT <K2JVMCompilerArguments>
+ val args = K2JVMCompilerArguments()
+
+ fun setOption(name: String, value: String) {
+ setOptionReflective(args, name, value)
+ }
+
+ fun setOptionArray(name: String, values: List<String>) {
+ setOptionReflective(args, name, values)
+ }
+
+ while (xr.hasNext()) {
+ when (xr.next()) {
+ XMLStreamConstants.START_ELEMENT -> when (xr.localName) {
+ "option" -> {
+ val name = xr.getAttributeValue(null, "name") ?: run {
+ skipElement(xr)
+ continue
+ }
+ val valueAttr = xr.getAttributeValue(null, "value")
+ if (valueAttr != null) {
+ setOption(name, valueAttr)
+ skipElement(xr)
+ } else {
+ // The value is specified via nested <array><option value="..."/></array> or <list><option value="..."/></list>
+ var collected: MutableList<String>? = null
+ loop@ while (xr.hasNext()) {
+ when (xr.next()) {
+ XMLStreamConstants.START_ELEMENT -> if (xr.localName == "array" || xr.localName == "list") {
+ collected = readStringArray(xr)
+ } else {
+ skipElement(xr)
+ }
+ XMLStreamConstants.END_ELEMENT -> if (xr.localName == "option") {
+ break@loop
+ }
+ }
+ }
+ if (collected != null) setOptionArray(name, collected)
+ }
+ }
+ else -> {
+ skipElement(xr)
+ }
+ }
+ XMLStreamConstants.END_ELEMENT -> if (xr.localName == "K2JVMCompilerArguments") {
+ return args
+ }
+ }
+ }
+ error("Unexpected end of XML while reading <K2JVMCompilerArguments>")
+}
+
+private fun readStringArray(xr: XMLStreamReader): MutableList<String> {
+ // reader is positioned at START_ELEMENT <array>/<list>
+ val result = mutableListOf<String>()
+ while (xr.hasNext()) {
+ when (xr.next()) {
+ XMLStreamConstants.START_ELEMENT -> if (xr.localName == "option") {
+ xr.getAttributeValue(null, "value")?.let { result += it }
+ skipElement(xr)
+ } else {
+ skipElement(xr)
+ }
+ XMLStreamConstants.END_ELEMENT -> if (xr.localName == "array" || xr.localName == "list") return result
+ }
+ }
+ error("Unexpected end of XML while reading <array>")
+}
+
+private fun setOptionReflective(args: Any, name: String, value: Any) {
+ // Use Kotlin reflection to find a mutable property and set it with basic type conversions.
+ val anyProp = args::class.memberProperties.firstOrNull { it.name == name } ?: return
+
+ @Suppress("UNCHECKED_CAST")
+ val prop = anyProp as? KMutableProperty1<Any, Any?> ?: return
+
+ val returnType = prop.returnType
+ val classifier = returnType.classifier as? KClass<*>
+
+ val converted: Any? = try {
+ when (classifier) {
+ Boolean::class -> when (value) {
+ is Boolean -> value
+ is String -> value.toBooleanStrictOrNull() ?: value.equals("true", ignoreCase = true)
+ else -> false
+ }
+ String::class -> value.toString()
+ Array<Any>::class, Array<String>::class, Array::class -> {
+ // Expecting Array<String>
+ val componentIsString = returnType.arguments.firstOrNull()?.type?.classifier == String::class
+ if (componentIsString) {
+ when (value) {
+ is List<*> -> value.filterIsInstance<String>().toTypedArray()
+ is Array<*> -> value.filterIsInstance<String>().toTypedArray()
+ is String -> arrayOf(value)
+ else -> emptyArray<String>()
+ }
+ } else null
+ }
+ List::class, MutableList::class, Collection::class -> {
+ // Support List<String> and MutableList<String> properties
+ val elementClassifier = returnType.arguments.firstOrNull()?.type?.classifier as? KClass<*>
+ if (elementClassifier == String::class) {
+ when (value) {
+ is List<*> -> value.filterIsInstance<String>()
+ is Array<*> -> value.filterIsInstance<String>()
+ is String -> listOf(value)
+ else -> emptyList()
+ }
+ } else null
+ }
+ else -> value
+ }
+ } catch (_: Throwable) {
+ null
+ }
+
+ try {
+ prop.setter.call(args, converted)
+ } catch (_: Throwable) {
+ // ignore to keep compatibility if types don't match
+ }
+}
+
+private fun skipElement(xr: XMLStreamReader) {
+ // Assumes the reader is at START_ELEMENT; consumes until matching END_ELEMENT
+ var depth = 1
+ while (depth > 0 && xr.hasNext()) {
+ when (xr.next()) {
+ XMLStreamConstants.START_ELEMENT -> depth++
+ XMLStreamConstants.END_ELEMENT -> depth--
+ }
}
}
diff --git a/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/ModelDumpAndReadTest.kt b/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/ModelDumpAndReadTest.kt
index 4d6c135..c712ad6 100644
--- a/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/ModelDumpAndReadTest.kt
+++ b/compiler/fir/modularized-tests/tests/org/jetbrains/kotlin/fir/ModelDumpAndReadTest.kt
@@ -20,12 +20,16 @@
val mainKt = tmpdir.resolve("main.kt").apply {
writeText("fun main() {}")
}
+ val main2Kt = tmpdir.resolve("main2.kt").apply {
+ writeText("fun main2() {}")
+ }
val jarFile = tmpdir.resolve("output.jar")
val args = listOf(
"-d", jarFile.absolutePath,
"-XXdump-model=${tmpdir.absolutePath}",
"-module-name", "testmain",
- mainKt.absolutePath
+ mainKt.absolutePath,
+ main2Kt.absolutePath,
)
val res = CompilerTestUtil.executeCompiler(K2JVMCompiler(), args)
assertEquals(ExitCode.OK, res.second)
@@ -35,11 +39,84 @@
val arguments = moduleData.arguments as K2JVMCompilerArguments
assertEquals(jarFile.absolutePath, arguments.destination)
- assertEquals(mainKt.absolutePath, arguments.freeArgs.single())
+ assertEquals(listOf(mainKt.absolutePath, main2Kt.absolutePath), arguments.freeArgs)
assertEquals("testmain", arguments.moduleName)
assertEquals("testmain", moduleData.name)
- assertEquals(listOf(mainKt), moduleData.sources.toList())
+ assertEquals(listOf(mainKt, main2Kt), moduleData.sources.toList())
assertTrue(moduleData.classpath.any { it.name == "kotlin-stdlib.jar" })
}
+
+ fun testOldModelFormatDeserialization() {
+ // We don't want to bother with windows specifics yet, model dumping is unix-only now anyway.
+ if (SystemInfo.isWindows) return
+ val src1 = tmpdir.resolve("A.kt").apply { writeText("fun a() {}") }
+ val src2 = tmpdir.resolve("A2.kt").apply { writeText("fun a2() {}") }
+ val outDir = tmpdir.resolve("out").apply { mkdirs() }
+ val dest = tmpdir.resolve("out.jar").absolutePath
+ val cp1 = tmpdir.resolve("lib1.jar").absolutePath
+ val cp2 = tmpdir.resolve("lib2.jar").absolutePath
+ val xml = """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <modules>
+ <compilerArguments>
+ <K2JVMCompilerArguments>
+ <option name="moduleName" value="m1" />
+ <option name="destination" value="$dest" />
+ <option name="noStdlib" value="true" />
+ <option name="noJdk" value="true" />
+ <option name="noReflect" value="true" />
+ <option name="languageVersion" value="2.0" />
+ <option name="jvmTarget" value="17" />
+ <option name="reportOutputFiles" value="true" />
+ <option name="optIn">
+ <array>
+ <option value="kotlin.ExperimentalStdlibApi" />
+ <option value="kotlin.RequiresOptIn" />
+ </array>
+ </option>
+ <option name="freeArgs">
+ <list>
+ <option value="${src1.absolutePath}" />
+ <option value="${src2.absolutePath}" />
+ </list>
+ </option>
+ </K2JVMCompilerArguments>
+ </compilerArguments>
+ <module timestamp="1" name="m1" type="java-production" outputDir="${outDir.absolutePath}">
+ <sources path="${src1.absolutePath}" />
+ <sources path="${src2.absolutePath}" />
+ <classpath path="$cp1" />
+ <classpath path="$cp2" />
+ <friendDir path="${outDir.absolutePath}" />
+ <javaSourceRoots path="${tmpdir.absolutePath}" packagePrefix="com.example" />
+ <modularJdkRoot path="/fake/jdk" />
+ <useOptIn annotation="kotlin.ExperimentalStdlibApi" />
+ </module>
+ </modules>
+ """.trimIndent()
+ val xmlFile = tmpdir.resolve("model-oldformat-simple.xml").apply { writeText(xml) }
+ val modules = loadModuleDumpFile(xmlFile)
+ assertEquals(1, modules.size)
+ val m = modules.single()
+ assertEquals("m1", m.name)
+ assertEquals(listOf(src1, src2), m.sources.toList())
+ // classpath keeps order
+ val cpList = m.classpath.map { it.absolutePath }
+ assertTrue(cp1 in cpList && cp2 in cpList)
+
+ val arguments = m.arguments as K2JVMCompilerArguments
+ assertEquals("m1", arguments.moduleName)
+ assertEquals(dest, arguments.destination)
+ assertTrue(arguments.noStdlib)
+ assertTrue(arguments.noJdk)
+ assertTrue(arguments.noReflect)
+ assertEquals("2.0", arguments.languageVersion)
+ assertEquals("17", arguments.jvmTarget)
+ assertTrue(arguments.reportOutputFiles)
+ // optIn maps to list/array
+ assertTrue(arguments.optIn?.contains("kotlin.ExperimentalStdlibApi") == true)
+ assertEquals(listOf(src1.absolutePath, src2.absolutePath), arguments.freeArgs)
+ }
+
}
\ No newline at end of file