diff --git a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiCommandLineProcessor.kt b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiCommandLineProcessor.kt index feec26fac5a..ec7db93e295 100644 --- a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiCommandLineProcessor.kt +++ b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiCommandLineProcessor.kt @@ -42,13 +42,27 @@ class JvmAbiCommandLineProcessor : CommandLineProcessor { "outside of the compilation unit where it's declared.", false, ) + + val PRESERVE_DECLARATION_ORDER_OPTION: CliOption = + CliOption( + "preserveDeclarationOrder", + "true/false", + "Do not sort class members in output abi.jar. Legacy flag that allows tools that rely on methods/fields order in the source" + + " to keep working. Flipping this to true reduces stability of the ABI.", + false, + ) } override val pluginId: String get() = COMPILER_PLUGIN_ID override val pluginOptions: Collection - get() = listOf(OUTPUT_PATH_OPTION, REMOVE_DEBUG_INFO_OPTION, REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE_OPTION) + get() = listOf( + OUTPUT_PATH_OPTION, + REMOVE_DEBUG_INFO_OPTION, + REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE_OPTION, + PRESERVE_DECLARATION_ORDER_OPTION, + ) override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) { when (option) { @@ -58,6 +72,7 @@ class JvmAbiCommandLineProcessor : CommandLineProcessor { JvmAbiConfigurationKeys.REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE, value == "true" ) + PRESERVE_DECLARATION_ORDER_OPTION -> configuration.put(JvmAbiConfigurationKeys.PRESERVE_DECLARATION_ORDER, value == "true") else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") } } diff --git a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiComponentRegistrar.kt b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiComponentRegistrar.kt index c5f36159a45..89224137a5a 100644 --- a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiComponentRegistrar.kt +++ b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiComponentRegistrar.kt @@ -26,6 +26,7 @@ class JvmAbiComponentRegistrar : CompilerPluginRegistrar() { File(outputPath), builderExtension.abiClassInfo, messageCollector, configuration.getBoolean(JvmAbiConfigurationKeys.REMOVE_DEBUG_INFO), removeDataClassCopy, + configuration.getBoolean(JvmAbiConfigurationKeys.PRESERVE_DECLARATION_ORDER), ) ClassGeneratorExtension.registerExtension(builderExtension) diff --git a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiConfigurationKeys.kt b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiConfigurationKeys.kt index 21b3c437288..fa22aa78442 100644 --- a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiConfigurationKeys.kt +++ b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiConfigurationKeys.kt @@ -14,4 +14,6 @@ object JvmAbiConfigurationKeys { CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.REMOVE_DEBUG_INFO_OPTION.description) val REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE: CompilerConfigurationKey = CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE_OPTION.description) + val PRESERVE_DECLARATION_ORDER: CompilerConfigurationKey = + CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.PRESERVE_DECLARATION_ORDER_OPTION.description) } diff --git a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt index 909b15c3175..aa13b689277 100644 --- a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt +++ b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt @@ -18,6 +18,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes fun abiMetadataProcessor( annotationVisitor: AnnotationVisitor, removeDataClassCopyIfConstructorIsPrivate: Boolean, + preserveDeclarationOrder: Boolean, ): AnnotationVisitor = kotlinClassHeaderVisitor { header -> // kotlinx-metadata only supports writing Kotlin metadata of version >= 1.4, so we need to @@ -32,13 +33,13 @@ fun abiMetadataProcessor( KotlinClassMetadata.transform(header) { metadata -> when (metadata) { is KotlinClassMetadata.Class -> { - metadata.kmClass.removePrivateDeclarations(removeDataClassCopyIfConstructorIsPrivate) + metadata.kmClass.removePrivateDeclarations(removeDataClassCopyIfConstructorIsPrivate, preserveDeclarationOrder) } is KotlinClassMetadata.FileFacade -> { - metadata.kmPackage.removePrivateDeclarations() + metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder) } is KotlinClassMetadata.MultiFileClassPart -> { - metadata.kmPackage.removePrivateDeclarations() + metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder) } else -> Unit } @@ -149,27 +150,27 @@ private fun AnnotationVisitor.visitKotlinMetadata(header: Metadata) { visitEnd() } -private fun KmClass.removePrivateDeclarations(removeCopyAlongWithConstructor: Boolean) { +private fun KmClass.removePrivateDeclarations(removeCopyAlongWithConstructor: Boolean, preserveDeclarationOrder: Boolean) { constructors.removeIf { it.visibility.isPrivate } - (this as KmDeclarationContainer).removePrivateDeclarations( - copyFunShouldBeDeleted(removeCopyAlongWithConstructor) - ) + (this as KmDeclarationContainer).removePrivateDeclarations(copyFunShouldBeDeleted(removeCopyAlongWithConstructor), preserveDeclarationOrder) localDelegatedProperties.clear() // TODO: do not serialize private type aliases once KT-17229 is fixed. } -private fun KmPackage.removePrivateDeclarations() { - (this as KmDeclarationContainer).removePrivateDeclarations(false) +private fun KmPackage.removePrivateDeclarations(preserveDeclarationOrder: Boolean) { + (this as KmDeclarationContainer).removePrivateDeclarations(false, preserveDeclarationOrder) localDelegatedProperties.clear() // TODO: do not serialize private type aliases once KT-17229 is fixed. } -private fun KmDeclarationContainer.removePrivateDeclarations(copyFunShouldBeDeleted: Boolean) { +private fun KmDeclarationContainer.removePrivateDeclarations(copyFunShouldBeDeleted: Boolean, preserveDeclarationOrder: Boolean) { functions.removeIf { it.visibility.isPrivate || (copyFunShouldBeDeleted && it.name == "copy") } properties.removeIf { it.visibility.isPrivate } - functions.sortWith(compareBy(KmFunction::name, { it.signature.toString() })) - properties.sortWith(compareBy(KmProperty::name, { it.getterSignature.toString() })) + if (!preserveDeclarationOrder) { + functions.sortWith(compareBy(KmFunction::name, { it.signature.toString() })) + properties.sortWith(compareBy(KmProperty::name, { it.getterSignature.toString() })) + } for (property in properties) { // Whether or not the *non-const* property is initialized by a compile-time constant is not a part of the ABI. diff --git a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt index b3f0bd424c0..84d2a27343b 100644 --- a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt +++ b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt @@ -28,11 +28,13 @@ class JvmAbiOutputExtension( private val messageCollector: MessageCollector, private val removeDebugInfo: Boolean, private val removeDataClassCopyIfConstructorIsPrivate: Boolean, + private val preserveDeclarationOrder: Boolean, ) : ClassFileFactoryFinalizerExtension { override fun finalizeClassFactory(factory: ClassFileFactory) { // We need to wait until the end to produce any output in order to strip classes // from the InnerClasses attributes. - val outputFiles = AbiOutputFiles(abiClassInfos, factory, removeDebugInfo, removeDataClassCopyIfConstructorIsPrivate) + val outputFiles = + AbiOutputFiles(abiClassInfos, factory, removeDebugInfo, removeDataClassCopyIfConstructorIsPrivate, preserveDeclarationOrder) if (outputPath.extension == "jar") { // We don't include the runtime or main class in interface jars and always reset time stamps. CompileEnvironmentUtil.writeToJar( @@ -56,6 +58,7 @@ class JvmAbiOutputExtension( val outputFiles: OutputFileCollection, val removeDebugInfo: Boolean, val removeCopyAlongWithConstructor: Boolean, + val preserveDeclarationOrder: Boolean, ) : OutputFileCollection { override fun get(relativePath: String): OutputFile? { error("AbiOutputFiles does not implement `get`.") @@ -155,14 +158,19 @@ class JvmAbiOutputExtension( if (descriptor != JvmAnnotationNames.METADATA_DESC) return delegate // Strip private declarations from the Kotlin Metadata annotation. - return abiMetadataProcessor(delegate, removeCopyAlongWithConstructor) + return abiMetadataProcessor(delegate, removeCopyAlongWithConstructor, preserveDeclarationOrder) } override fun visitEnd() { // Output class members in sorted order so that changes in original ordering don't affect the ABI JAR. - keptFields.sortedWith(compareBy(FieldNode::name, FieldNode::desc)).forEach { it.accept(cv) } - keptMethods.sortedWith(compareBy(MethodNode::name, MethodNode::desc)).forEach { it.accept(cv) } + keptFields + .apply { if (!preserveDeclarationOrder) sortWith(compareBy(FieldNode::name, FieldNode::desc)) } + .forEach { it.accept(cv) } + + keptMethods + .apply { if (!preserveDeclarationOrder) sortWith(compareBy(MethodNode::name, MethodNode::desc)) } + .forEach { it.accept(cv) } innerClassesToKeep.addInnerClasses(innerClassInfos, internalName) innerClassesToKeep.addOuterClasses(innerClassInfos) diff --git a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/BaseJvmAbiTest.kt b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/BaseJvmAbiTest.kt index 682b68bcd16..5c1364b29ed 100644 --- a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/BaseJvmAbiTest.kt +++ b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/BaseJvmAbiTest.kt @@ -84,7 +84,10 @@ abstract class BaseJvmAbiTest : TestCase() { JvmAbiCommandLineProcessor.REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE_OPTION.optionName, true.toString() ).takeIf { InTextDirectivesUtils.findStringWithPrefixes(directives, "// REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE") != null - } + }, + abiOption(JvmAbiCommandLineProcessor.PRESERVE_DECLARATION_ORDER_OPTION.optionName, true.toString()).takeIf { + InTextDirectivesUtils.findStringWithPrefixes(directives, "// PRESERVE_DECLARATION_ORDER") != null + }, ).toTypedArray() destination = compilation.destinationDir.canonicalPath noSourceDebugExtension = InTextDirectivesUtils.findStringWithPrefixes(directives, "// NO_SOURCE_DEBUG_EXTENSION") != null diff --git a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/CompareJvmAbiTestGenerated.java b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/CompareJvmAbiTestGenerated.java index b0b74f5c5b8..1bb76a651a1 100644 --- a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/CompareJvmAbiTestGenerated.java +++ b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/CompareJvmAbiTestGenerated.java @@ -145,6 +145,11 @@ public class CompareJvmAbiTestGenerated extends AbstractCompareJvmAbiTest { runTest("plugins/jvm-abi-gen/testData/compare/parameterName/"); } + @TestMetadata("preserveDeclarationOrder") + public void testPreserveDeclarationOrder() throws Exception { + runTest("plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/"); + } + @TestMetadata("privateTypealias") public void testPrivateTypealias() throws Exception { runTest("plugins/jvm-abi-gen/testData/compare/privateTypealias/"); diff --git a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java index 4cca0a81de0..bc405c6a71c 100644 --- a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java +++ b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java @@ -65,6 +65,11 @@ public class JvmAbiContentTestGenerated extends AbstractJvmAbiContentTest { runTest("plugins/jvm-abi-gen/testData/content/kt50005/"); } + @TestMetadata("preserveDeclarationOrderKeepsClassIntact") + public void testPreserveDeclarationOrderKeepsClassIntact() throws Exception { + runTest("plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/"); + } + @TestMetadata("whenMapping") public void testWhenMapping() throws Exception { runTest("plugins/jvm-abi-gen/testData/content/whenMapping/"); diff --git a/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/base/test.kt b/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/base/test.kt new file mode 100644 index 00000000000..ca88bafb454 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/base/test.kt @@ -0,0 +1,11 @@ +package test + +class Chaos { + fun a() = Unit + fun b() = Unit + + @JvmField + val a = Unit + @JvmField + val b = Unit +} diff --git a/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/differentAbi/test.kt b/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/differentAbi/test.kt new file mode 100644 index 00000000000..7a9cf215d58 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/differentAbi/test.kt @@ -0,0 +1,11 @@ +package test + +class Chaos { + fun b() = Unit + fun a() = Unit + + @JvmField + val b = Unit + @JvmField + val a = Unit +} diff --git a/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/directives.txt b/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/directives.txt new file mode 100644 index 00000000000..9d0afd05e21 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/directives.txt @@ -0,0 +1 @@ +// PRESERVE_DECLARATION_ORDER diff --git a/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/directives.txt b/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/directives.txt new file mode 100644 index 00000000000..9d0afd05e21 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/directives.txt @@ -0,0 +1 @@ +// PRESERVE_DECLARATION_ORDER diff --git a/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/preserveDeclarationOrder.kt b/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/preserveDeclarationOrder.kt new file mode 100644 index 00000000000..7a9cf215d58 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/preserveDeclarationOrder.kt @@ -0,0 +1,11 @@ +package test + +class Chaos { + fun b() = Unit + fun a() = Unit + + @JvmField + val b = Unit + @JvmField + val a = Unit +} diff --git a/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/signatures.txt b/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/signatures.txt new file mode 100644 index 00000000000..1ecda79f7f9 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/preserveDeclarationOrderKeepsClassIntact/signatures.txt @@ -0,0 +1,9 @@ +@kotlin.Metadata +public final class test/Chaos { + // source: 'preserveDeclarationOrder.kt' + public final @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull field b: kotlin.Unit + public final @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull field a: kotlin.Unit + public method (): void + public final method b(): void + public final method a(): void +}