From 5fcdd4a9f635900ab537773bcd007399f2d5cb3b Mon Sep 17 00:00:00 2001 From: Artem Olkov Date: Wed, 13 Dec 2023 10:40:38 +0000 Subject: [PATCH] KT-63748: Pack Swift Export Frontend into compiler plugin Co-authored-by: Sergej Jaskiewicz Merge-request: KT-MR-13351 Merged-by: Artem Olkov --- .space/CODEOWNERS | 1 + .../sir-tests-generator/build.gradle.kts | 1 + .../native/swift/sir/GenerateSirTests.kt | 13 +- .../SirAnalysisGeneratedTests.java | 2 +- .../sir/passes/SirInflatePackagesPass.kt | 4 +- .../src/org/jetbrains/sir/passes/SirPass.kt | 5 +- ...ForeignIntoSwiftFunctionTranslationPass.kt | 4 +- .../kotlin/sir/passes/SirPassTests.kt | 5 +- .../sir/printer/SirAsSwiftSourcesPrinter.kt | 9 + plugins/swift-export/README.md | 67 +++++++ plugins/swift-export/build.gradle.kts | 59 ++++++ .../swift-export.backend/build.gradle.kts | 34 ++++ .../swiftexport/SwiftExportExtension.kt | 175 ++++++++++++++++++ .../swiftexport/SwiftExportPluginNames.kt | 12 ++ .../swift-export.cli/build.gradle.kts | 34 ++++ ...otlin.compiler.plugin.CommandLineProcessor | 1 + ...in.compiler.plugin.CompilerPluginRegistrar | 1 + .../kotlin/swiftexport/SwiftExportPlugin.kt | 68 +++++++ .../swift-export.embeddable/build.gradle.kts | 21 +++ .../JvmCompilerWithSwiftExportPluginFacade.kt | 111 +++++++++++ .../swiftexport/Kotlin2SwiftExportTests.kt | 50 +++++ .../kotlin/swiftexport/SirExportHandler.kt | 37 ++++ .../kotlin/swiftexport/SwiftExportArtifact.kt | 21 +++ plugins/swift-export/testData/simple.golden.h | 7 + .../swift-export/testData/simple.golden.kt | 19 ++ .../swift-export/testData/simple.golden.swift | 10 + plugins/swift-export/testData/simple.kt | 16 ++ .../SwiftExportCompilerPluginTest.java | 33 ++++ settings.gradle | 10 + 29 files changed, 821 insertions(+), 9 deletions(-) create mode 100644 plugins/swift-export/README.md create mode 100644 plugins/swift-export/build.gradle.kts create mode 100644 plugins/swift-export/swift-export.backend/build.gradle.kts create mode 100644 plugins/swift-export/swift-export.backend/src/org/jetbrains/kotlin/swiftexport/SwiftExportExtension.kt create mode 100644 plugins/swift-export/swift-export.backend/src/org/jetbrains/kotlin/swiftexport/SwiftExportPluginNames.kt create mode 100644 plugins/swift-export/swift-export.cli/build.gradle.kts create mode 100644 plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor create mode 100644 plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar create mode 100644 plugins/swift-export/swift-export.cli/src/org/jetbrains/kotlin/swiftexport/SwiftExportPlugin.kt create mode 100644 plugins/swift-export/swift-export.embeddable/build.gradle.kts create mode 100644 plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/JvmCompilerWithSwiftExportPluginFacade.kt create mode 100644 plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/Kotlin2SwiftExportTests.kt create mode 100644 plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SirExportHandler.kt create mode 100644 plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SwiftExportArtifact.kt create mode 100644 plugins/swift-export/testData/simple.golden.h create mode 100644 plugins/swift-export/testData/simple.golden.kt create mode 100644 plugins/swift-export/testData/simple.golden.swift create mode 100644 plugins/swift-export/testData/simple.kt create mode 100644 plugins/swift-export/tests-gen/org/jetbrains/kotlin/swiftexport/SwiftExportCompilerPluginTest.java diff --git a/.space/CODEOWNERS b/.space/CODEOWNERS index 701b12c2b3f..f1930eec84b 100644 --- a/.space/CODEOWNERS +++ b/.space/CODEOWNERS @@ -379,6 +379,7 @@ /plugins/power-assert/ "Kotlin Compiler Core" /plugins/sam-with-receiver/ "Kotlin Compiler Core" /plugins/scripting/ "Kotlin Compiler Core" +/plugins/swift-export/ "Kotlin Native" /prepare/ "Kotlin Build Infrastructure" diff --git a/generators/sir-tests-generator/build.gradle.kts b/generators/sir-tests-generator/build.gradle.kts index 72063e589a2..f8b495cd8bc 100644 --- a/generators/sir-tests-generator/build.gradle.kts +++ b/generators/sir-tests-generator/build.gradle.kts @@ -9,6 +9,7 @@ sourceSets { dependencies { implementation(projectTests(":native:swift:sir-analysis-api")) implementation(projectTests(":native:swift:sir-compiler-bridge")) + implementation(projectTests(":kotlin-swift-export-compiler-plugin")) implementation(projectTests(":generators:test-generator")) runtimeOnly(projectTests(":analysis:analysis-test-framework")) runtimeOnly(libs.junit.jupiter.api) diff --git a/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt b/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt index 06873f53af4..5582c6d7624 100644 --- a/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt +++ b/generators/sir-tests-generator/main/org/jetbrains/kotlin/generators/tests/native/swift/sir/GenerateSirTests.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.generators.tests.native.swift.sir import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5 import org.jetbrains.kotlin.sir.analysisapi.AbstractKotlinSirContextTest import org.jetbrains.kotlin.sir.bridge.AbstractKotlinSirBridgeTest +import org.jetbrains.kotlin.swiftexport.AbstractSwiftExportContextTest fun main() { @@ -20,7 +21,7 @@ fun main() { testClass( suiteTestClassName = "SirAnalysisGeneratedTests" ) { - model("", pattern = "^([^_](.+)).kt$", recursive = false) + model("", recursive = false) } } testGroup( @@ -33,5 +34,15 @@ fun main() { model("", extension = null, recursive = false) } } + testGroup( + "plugins/swift-export/tests-gen", + "plugins/swift-export/testData" + ) { + testClass( + suiteTestClassName = "SwiftExportCompilerPluginTest" + ) { + model("", excludedPattern = ".*\\.golden\\.kt$", recursive = false) + } + } } } diff --git a/native/swift/sir-analysis-api/tests-gen/org/jetbrains/kotlin/sir/analysisapi/SirAnalysisGeneratedTests.java b/native/swift/sir-analysis-api/tests-gen/org/jetbrains/kotlin/sir/analysisapi/SirAnalysisGeneratedTests.java index 47422ab4cf4..91ce8ae2097 100644 --- a/native/swift/sir-analysis-api/tests-gen/org/jetbrains/kotlin/sir/analysisapi/SirAnalysisGeneratedTests.java +++ b/native/swift/sir-analysis-api/tests-gen/org/jetbrains/kotlin/sir/analysisapi/SirAnalysisGeneratedTests.java @@ -21,7 +21,7 @@ import java.util.regex.Pattern; public class SirAnalysisGeneratedTests extends AbstractKotlinSirContextTest { @Test public void testAllFilesPresentInTestData() throws Exception { - KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/swift/sir-analysis-api/testData"), Pattern.compile("^([^_](.+)).kt$"), null, false); + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/swift/sir-analysis-api/testData"), Pattern.compile("^(.+)\\.kt$"), null, false); } @Test diff --git a/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirInflatePackagesPass.kt b/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirInflatePackagesPass.kt index 2990eef69dd..cdad10f35bf 100644 --- a/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirInflatePackagesPass.kt +++ b/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirInflatePackagesPass.kt @@ -14,7 +14,7 @@ import org.jetbrains.kotlin.sir.visitors.SirTransformer * Pass that for every occurring declaration in package x.y.z generates a mirroring type scope and puts it there. * Right now, enums without cases are used for namespace simulation. */ -public class SirInflatePackagesPass : SirPass { +public class SirInflatePackagesPass : SirModulePass { private data class Namespace( val elements: MutableList = mutableListOf(), val children: MutableMap = mutableMapOf(), @@ -79,7 +79,7 @@ public class SirInflatePackagesPass : SirPass { }.also(SirDeclarationContainer::fixParents) } - public override fun run(element: SirModule, data: Unit): SirModule = element.transform(Transformer, Context()) + public override fun run(element: SirModule, data: Nothing?): SirModule = element.transform(Transformer, Context()) } private fun SirDeclarationContainer.fixParents() = declarations diff --git a/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirPass.kt b/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirPass.kt index 44fafa6b241..ecf2a525919 100644 --- a/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirPass.kt +++ b/native/swift/sir-passes/src/org/jetbrains/sir/passes/SirPass.kt @@ -6,6 +6,9 @@ package org.jetbrains.sir.passes import org.jetbrains.kotlin.sir.SirElement +import org.jetbrains.kotlin.sir.SirModule + +public typealias SirModulePass = SirPass /** * Swift IR is supposed to be transformed by a series of passes. @@ -23,4 +26,4 @@ public interface SirPass { public fun run(element: E, data: T): R } -public fun SirPass.run(element: E): R = this.run(element, Unit) \ No newline at end of file +public fun SirPass.run(element: E): R = this.run(element, null) diff --git a/native/swift/sir-passes/src/org/jetbrains/sir/passes/translation/ForeignIntoSwiftFunctionTranslationPass.kt b/native/swift/sir-passes/src/org/jetbrains/sir/passes/translation/ForeignIntoSwiftFunctionTranslationPass.kt index 45260d4b0b9..95f59527eef 100644 --- a/native/swift/sir-passes/src/org/jetbrains/sir/passes/translation/ForeignIntoSwiftFunctionTranslationPass.kt +++ b/native/swift/sir-passes/src/org/jetbrains/sir/passes/translation/ForeignIntoSwiftFunctionTranslationPass.kt @@ -21,7 +21,7 @@ import java.lang.IllegalStateException * or `element` does not contain origin of type `SirOrigin.KotlinEntity.Function`, * returns original element. */ -public class ForeignIntoSwiftFunctionTranslationPass : SirPass { +public class ForeignIntoSwiftFunctionTranslationPass : SirPass { private class Transformer : SirTransformerVoid() { override fun transformElement(element: E): E { @@ -45,7 +45,7 @@ public class ForeignIntoSwiftFunctionTranslationPass : SirPass() + + return buildModule { + name = sourceModule.moduleName + val sirFactory = SirGenerator() + ktFiles.forEach { file -> + declarations += sirFactory.build(file) + } + }.apply { + declarations.forEach { it.parent = this } + } + } + + private fun SirModule.transformToSwift(): SirModule { + return SirPassesConfiguration.passes.fold(this) { module, pass -> + pass.run(module) + } + } + + private fun SirModule.dumpResultToFiles() { + val destinationPath = destination.absolutePath + + val cHeaderFile = File("${destinationPath}/${outputFileName}.h") + val ktBridgeFile = File("${destinationPath}/${outputFileName}.kt") + val swiftFile = File("${destinationPath}/${outputFileName}.swift") + + val bridges = generateBridgeSources() + val swiftSrc = generateSwiftSrc() + + dumpTextAtFile(bridges.ktSrc, ktBridgeFile) + dumpTextAtFile(bridges.cSrc, cHeaderFile) + dumpTextAtFile(sequenceOf(swiftSrc), swiftFile) + } + + private fun SirModule.generateSwiftSrc(): String { + return SirAsSwiftSourcesPrinter().print(this) + } + + private fun SirModule.generateBridgeSources(): BridgeSources { + val requests = BridgeRequestsBuilder.build(this) + + val generator = createBridgeGenerator() + val kotlinBridgePrinter = createKotlinBridgePrinter() + val cBridgePrinter = createCBridgePrinter() + + requests.forEach { request -> + val bridge = generator.generate(request) + kotlinBridgePrinter.add(bridge) + cBridgePrinter.add(bridge) + } + + val actualKotlinSrc = kotlinBridgePrinter.print() + val actualCHeader = cBridgePrinter.print() + + return BridgeSources(ktSrc = actualKotlinSrc, cSrc = actualCHeader) + } + + private fun dumpTextAtFile(text: Sequence, file: File) { + if (!file.exists()) { + file.parentFile.mkdirs() + file.createNewFile() + } + val writer = file.printWriter() + for (t in text) { + writer.println(t) + } + writer.close() + } + + private data class BridgeSources(val ktSrc: Sequence, val cSrc: Sequence) + + private object SirPassesConfiguration { + val passes: List = listOf( + SirInflatePackagesPass(), + WholeModuleTranslationByElementPass(ForeignIntoSwiftFunctionTranslationPass()), + ) + + class WholeModuleTranslationByElementPass( + val pass: SirPass + ) : SirModulePass { + override fun run(element: SirModule, data: Nothing?): SirModule { + return buildModule { + name = element.name + element.declarations.forEach { + val newDecl = pass.run(it) + declarations.add(newDecl) + } + }.apply { + declarations.forEach { it.parent = this } + } + } + } + } + + private object BridgeRequestsBuilder : SirVisitor>() { + fun build(from: SirModule): List { + val result = mutableListOf() + from.accept(this, result) + return result + } + + override fun visitFunction(function: SirFunction, data: MutableList) { + val fqName = (function.origin as? SirKotlinOrigin.Function)?.fqName ?: return + + data.add( + BridgeRequest( + function, + fqName.joinToString("_"), + fqName + ) + ) + } + + override fun visitElement(element: SirElement, data: MutableList) { + element.acceptChildren(this, data) + } + } +} diff --git a/plugins/swift-export/swift-export.backend/src/org/jetbrains/kotlin/swiftexport/SwiftExportPluginNames.kt b/plugins/swift-export/swift-export.backend/src/org/jetbrains/kotlin/swiftexport/SwiftExportPluginNames.kt new file mode 100644 index 00000000000..7d6188c963d --- /dev/null +++ b/plugins/swift-export/swift-export.backend/src/org/jetbrains/kotlin/swiftexport/SwiftExportPluginNames.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.swiftexport + +object SwiftExportPluginNames { + const val PLUGIN_ID = "org.jetbrains.kotlin.swiftexport" + const val OUTPUT_DIR_KEY = "output_dir" + const val RESULT_NAME_KEY = "named" +} diff --git a/plugins/swift-export/swift-export.cli/build.gradle.kts b/plugins/swift-export/swift-export.cli/build.gradle.kts new file mode 100644 index 00000000000..4fe44887d37 --- /dev/null +++ b/plugins/swift-export/swift-export.cli/build.gradle.kts @@ -0,0 +1,34 @@ +description = "Swift Compiler Plugin (CLI)" + +plugins { + kotlin("jvm") +} + +dependencies { + implementation(project(":kotlin-swift-export-compiler-plugin.backend")) + + compileOnly(project(":compiler:plugin-api")) + compileOnly(project(":compiler:fir:entrypoint")) + + embedded(project(":analysis:analysis-api-standalone")) { isTransitive = false } + embedded(project(":analysis:analysis-api")) { isTransitive = false } + embedded(project(":analysis:analysis-api-fir")) { isTransitive = false } + embedded(project(":analysis:analysis-api-impl-base")) { isTransitive = false } + embedded(project(":analysis:analysis-api-impl-barebone")) { isTransitive = false } + embedded(project(":analysis:analysis-api-standalone:analysis-api-standalone-base")) { isTransitive = false } + embedded(project(":analysis:analysis-api-standalone:analysis-api-fir-standalone-base")) { isTransitive = false } + embedded(project(":analysis:analysis-internal-utils")) { isTransitive = false } + embedded(project(":analysis:low-level-api-fir")) { isTransitive = false } + embedded(project(":analysis:symbol-light-classes")) { isTransitive = false } +} + +optInToExperimentalCompilerApi() + +sourceSets { + "main" { projectDefault() } + "test" { none() } +} + +runtimeJar() +sourcesJar() +javadocJar() diff --git a/plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor b/plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor new file mode 100644 index 00000000000..380b42f7607 --- /dev/null +++ b/plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor @@ -0,0 +1 @@ +org.jetbrains.kotlin.swiftexport.SwiftExportCommandLineProcessor \ No newline at end of file diff --git a/plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar b/plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar new file mode 100644 index 00000000000..b003497d914 --- /dev/null +++ b/plugins/swift-export/swift-export.cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar @@ -0,0 +1 @@ +org.jetbrains.kotlin.swiftexport.SwiftExportComponentRegistrar \ No newline at end of file diff --git a/plugins/swift-export/swift-export.cli/src/org/jetbrains/kotlin/swiftexport/SwiftExportPlugin.kt b/plugins/swift-export/swift-export.cli/src/org/jetbrains/kotlin/swiftexport/SwiftExportPlugin.kt new file mode 100644 index 00000000000..ea02c5e2b9b --- /dev/null +++ b/plugins/swift-export/swift-export.cli/src/org/jetbrains/kotlin/swiftexport/SwiftExportPlugin.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.swiftexport + +import org.jetbrains.kotlin.compiler.plugin.* +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.CompilerConfigurationKey +import org.jetbrains.kotlin.fir.extensions.FirAnalysisHandlerExtension +import org.jetbrains.kotlin.swiftexport.SwiftExportPluginNames.OUTPUT_DIR_KEY +import org.jetbrains.kotlin.swiftexport.SwiftExportPluginNames.PLUGIN_ID +import org.jetbrains.kotlin.swiftexport.SwiftExportPluginNames.RESULT_NAME_KEY +import java.io.File + +object SwiftExportConfigurationKeys { + val OUTPUT_DIR: CompilerConfigurationKey = CompilerConfigurationKey.create( + "Destination directory for swift-export resulted files" + ) + val RESULT_NAME: CompilerConfigurationKey = CompilerConfigurationKey.create( + "Filenames for resulted files. There will be 3 files - %RESULT_NAME%.swift, %RESULT_NAME%.h and %RESULT_NAME%.kt." + ) +} + +class SwiftExportCommandLineProcessor : CommandLineProcessor { + companion object { + val OUTPUT_DIR = CliOption( + OUTPUT_DIR_KEY, "output destination", + "Destination directory for swift-export resulted files", + required = true, allowMultipleOccurrences = false + ) + val RESULT_NAME = CliOption( + RESULT_NAME_KEY, "Filenames for resulted files", + "Filenames for resulted files. By default - \"result\"", + required = false, allowMultipleOccurrences = false + ) + } + + override val pluginId = PLUGIN_ID + override val pluginOptions = listOf( + OUTPUT_DIR, RESULT_NAME + ) + + override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) = when (option) { + OUTPUT_DIR -> configuration.put(SwiftExportConfigurationKeys.OUTPUT_DIR, value) + RESULT_NAME -> configuration.put(SwiftExportConfigurationKeys.RESULT_NAME, value) + else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") + } +} + +class SwiftExportComponentRegistrar : CompilerPluginRegistrar() { + override val supportsK2: Boolean + get() = true + + override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { + val dir = File( + configuration.get(SwiftExportConfigurationKeys.OUTPUT_DIR) + ?: throw IllegalArgumentException("output_dir is a required argument for org.jetbrains.kotlin.swiftexport") + ) + + val named = configuration.get(SwiftExportConfigurationKeys.RESULT_NAME) + ?: "result" + FirAnalysisHandlerExtension.registerExtension( + SwiftExportExtension(dir, named) + ) + } +} diff --git a/plugins/swift-export/swift-export.embeddable/build.gradle.kts b/plugins/swift-export/swift-export.embeddable/build.gradle.kts new file mode 100644 index 00000000000..102714e7577 --- /dev/null +++ b/plugins/swift-export/swift-export.embeddable/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("org.jetbrains.kotlin.jvm") +} + +dependencies { + embedded(project(":kotlin-swift-export-compiler-plugin")) { isTransitive = false } +} + +if (project.hasProperty("kotlin-native.swift-export.enabled")) { + publish { + artifactId = "kotlin-swift-export-compiler-plugin-embeddable" + } +} + +runtimeJar(rewriteDefaultJarDepsToShadedCompiler()) +sourcesJarWithSourcesFromEmbedded( + project(":kotlin-swift-export-compiler-plugin").tasks.named("sourcesJar") +) +javadocJarWithJavadocFromEmbedded( + project(":kotlin-swift-export-compiler-plugin").tasks.named("javadocJar") +) diff --git a/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/JvmCompilerWithSwiftExportPluginFacade.kt b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/JvmCompilerWithSwiftExportPluginFacade.kt new file mode 100644 index 00000000000..b1d03db2daa --- /dev/null +++ b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/JvmCompilerWithSwiftExportPluginFacade.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.swiftexport + +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.kotlin.cli.common.CLITool +import org.jetbrains.kotlin.cli.common.ExitCode +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.CompilerTestUtil +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.compilerConfigurationProvider +import org.jetbrains.kotlin.test.services.getKtFilesForSourceFiles +import org.jetbrains.kotlin.test.services.sourceFileProvider +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import kotlin.reflect.KClass +import kotlin.test.assertEquals + +internal class JvmCompilerWithSwiftExportPluginFacade( + private val testServices: TestServices, +) : AbstractTestFacade() { + override val inputKind: TestArtifactKind + get() = SourcesKind + override val outputKind: TestArtifactKind + get() = SwiftExportArtifact.Kind + + private val tmpDir = FileUtil.createTempDirectory("SwiftExportIntegrationTests", null, false) + + override fun transform(module: TestModule, inputArtifact: ResultingArtifact.Source): SwiftExportArtifact { + val configurationProvider = testServices.compilerConfigurationProvider + val project = configurationProvider.getProject(module) + val ktFiles = testServices.sourceFileProvider.getKtFilesForSourceFiles(module.files, project, findViaVfs = true).values.toList() + + val outputDirPath = tmpDir.absolutePath + "/" + "swift_export_output" + val outputDir = File(outputDirPath) + outputDir.mkdirs() + + runTest( + K2JVMCompiler(), + ktFiles, + outputDir + ) + + return SwiftExportArtifact( + File(outputDir.absolutePath + "/result.swift"), + File(outputDir.absolutePath + "/result.h"), + File(outputDir.absolutePath + "/result.kt"), + ) + } + + private fun runTest( + compiler: CLITool<*>, + src: List, + outputDir: File, + ) { + val sources = src.map { + tmpDir.resolve(it.name).apply { + writeText(it.text) + } + } + + val plugin = writePlugin( + SwiftExportCommandLineProcessor::class, + SwiftExportComponentRegistrar::class, + ) + val args = listOf( + "-Xplugin=$plugin", + "-P", "plugin:org.jetbrains.kotlin.swiftexport:output_dir=${outputDir}" + ) + sources.map { it.absolutePath } + + val outputPath = listOf( + "-language-version", "2.0", + "-d", tmpDir.resolve("out").absolutePath + ) + + val (output, exitCode) = CompilerTestUtil.executeCompiler( + compiler, + args + outputPath + ) + assertEquals(ExitCode.OK, exitCode, output) + } + + private fun writePlugin( + cliProcessor: KClass, + registrarKClass: KClass, + ): String { + val jarFile = tmpDir.resolve("plugin.jar") + ZipOutputStream(jarFile.outputStream()).use { + val entryRegistry = ZipEntry("META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar") + it.putNextEntry(entryRegistry) + it.write(registrarKClass.java.name.toByteArray()) + + val entryCLI = ZipEntry("META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor") + it.putNextEntry(entryCLI) + it.write(cliProcessor.java.name.toByteArray()) + } + return jarFile.absolutePath + } + + override fun shouldRunAnalysis(module: TestModule): Boolean { + return true + } +} \ No newline at end of file diff --git a/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/Kotlin2SwiftExportTests.kt b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/Kotlin2SwiftExportTests.kt new file mode 100644 index 00000000000..62123132d0f --- /dev/null +++ b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/Kotlin2SwiftExportTests.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.swiftexport + +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.runners.* +import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator +import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator + +open class AbstractSwiftExportContextTest : AbstractSwiftExportContextTestBase(TargetBackend.JVM) { + override fun configure(builder: TestConfigurationBuilder) { + super.configure(builder) + builder.apply { + globalDefaults { + targetBackend = TargetBackend.JVM + } + } + } +} + +abstract class AbstractSwiftExportContextTestBase( + targetBackend: TargetBackend +) : AbstractKotlinCompilerWithTargetBackendTest(targetBackend) { + + override fun TestConfigurationBuilder.configuration() { + globalDefaults { + frontend = FrontendKinds.FIR + targetPlatform = JvmPlatforms.defaultJvmPlatform + dependencyKind = DependencyKind.Binary + } + + useConfigurators( + ::CommonEnvironmentConfigurator, + ::JvmEnvironmentConfigurator, + ) + + facadeStep(::JvmCompilerWithSwiftExportPluginFacade) + + handlersStep(SwiftExportArtifact.Kind) { + useHandlers(::SirExportHandler) + } + } +} + diff --git a/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SirExportHandler.kt b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SirExportHandler.kt new file mode 100644 index 00000000000..e3fb171c529 --- /dev/null +++ b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SirExportHandler.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.swiftexport + +import org.jetbrains.kotlin.test.model.AnalysisHandler +import org.jetbrains.kotlin.test.model.TestArtifactKind +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.assertions +import java.io.File + +internal class SirExportHandler(testServices: TestServices) : AnalysisHandler( + testServices, + failureDisablesNextSteps = true, + doNotRunIfThereWerePreviousFailures = true +) { + override val artifactKind: TestArtifactKind + get() = SwiftExportArtifact.Kind + + override fun processModule(module: TestModule, info: SwiftExportArtifact) { + val originalFile = module.files.first().originalFile + val originalFileName = originalFile.absolutePath.removeSuffix(".kt") + + val expectedSwift = File("${originalFileName}.golden.swift") + val expectedCHeader = File("${originalFileName}.golden.h") + val expectedKotlinBridge = File("${originalFileName}.golden.kt") + + testServices.assertions.assertEqualsToFile(expectedSwift, info.swift.readText()) + testServices.assertions.assertEqualsToFile(expectedCHeader, info.cHeader.readText()) + testServices.assertions.assertEqualsToFile(expectedKotlinBridge, info.ktBridge.readText()) + } + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) {} +} \ No newline at end of file diff --git a/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SwiftExportArtifact.kt b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SwiftExportArtifact.kt new file mode 100644 index 00000000000..9d0f1abf835 --- /dev/null +++ b/plugins/swift-export/test/org/jetbrains/kotlin/swiftexport/SwiftExportArtifact.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.swiftexport + +import org.jetbrains.kotlin.test.model.BinaryKind +import org.jetbrains.kotlin.test.model.ResultingArtifact +import java.io.File + +internal data class SwiftExportArtifact( + val swift: File, + val cHeader: File, + val ktBridge: File, +) : ResultingArtifact.Binary() { + object Kind : BinaryKind("SwiftExportArtifact") + + override val kind: BinaryKind + get() = Kind +} \ No newline at end of file diff --git a/plugins/swift-export/testData/simple.golden.h b/plugins/swift-export/testData/simple.golden.h new file mode 100644 index 00000000000..bdc83244c70 --- /dev/null +++ b/plugins/swift-export/testData/simple.golden.h @@ -0,0 +1,7 @@ +#include + +int32_t namespace1_main_foobar(); + +int32_t namespace1_foo(); + +int32_t namespace2_bar(); diff --git a/plugins/swift-export/testData/simple.golden.kt b/plugins/swift-export/testData/simple.golden.kt new file mode 100644 index 00000000000..78dbac7e167 --- /dev/null +++ b/plugins/swift-export/testData/simple.golden.kt @@ -0,0 +1,19 @@ +import kotlin.native.internal.ExportForCppRuntime + +@ExportForCppRuntime("namespace1_main_foobar") +public fun namespace1_main_foobar(): Int { + val result = namespace1.main.foobar() + return result +} + +@ExportForCppRuntime("namespace1_foo") +public fun namespace1_foo(): Int { + val result = namespace1.foo() + return result +} + +@ExportForCppRuntime("namespace2_bar") +public fun namespace2_bar(): Int { + val result = namespace2.bar() + return result +} diff --git a/plugins/swift-export/testData/simple.golden.swift b/plugins/swift-export/testData/simple.golden.swift new file mode 100644 index 00000000000..79c0207109e --- /dev/null +++ b/plugins/swift-export/testData/simple.golden.swift @@ -0,0 +1,10 @@ +enum namespace1 { + enum main { + public func foobar() -> Swift.Int32 { fatalError() } + } + public func foo() -> Swift.Int32 { fatalError() } +} + +enum namespace2 { + public func bar() -> Swift.Int32 { fatalError() } +} diff --git a/plugins/swift-export/testData/simple.kt b/plugins/swift-export/testData/simple.kt new file mode 100644 index 00000000000..d5762729882 --- /dev/null +++ b/plugins/swift-export/testData/simple.kt @@ -0,0 +1,16 @@ +// WITH_STDLIB + +// FILE: foo.kt +package namespace1 +fun foo(): Int = 123 + +// FILE: bar.kt +package namespace2 +fun bar(): Int = 321 + +// FILE: main.kt +package namespace1.main + +import namespace1 +import namespace2 +fun foobar(): Int = foo() + bar() diff --git a/plugins/swift-export/tests-gen/org/jetbrains/kotlin/swiftexport/SwiftExportCompilerPluginTest.java b/plugins/swift-export/tests-gen/org/jetbrains/kotlin/swiftexport/SwiftExportCompilerPluginTest.java new file mode 100644 index 00000000000..83c2a361dd4 --- /dev/null +++ b/plugins/swift-export/tests-gen/org/jetbrains/kotlin/swiftexport/SwiftExportCompilerPluginTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.swiftexport; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.native.swift.sir.GenerateSirTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/swift-export/testData") +@TestDataPath("$PROJECT_ROOT") +public class SwiftExportCompilerPluginTest extends AbstractSwiftExportContextTest { + @Test + public void testAllFilesPresentInTestData() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/swift-export/testData"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile(".*\\.golden\\.kt$"), TargetBackend.JVM, false); + } + + @Test + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("plugins/swift-export/testData/simple.kt"); + } +} diff --git a/settings.gradle b/settings.gradle index 1be50a316cb..e0e60657c71 100644 --- a/settings.gradle +++ b/settings.gradle @@ -152,6 +152,11 @@ include ":kotlin-noarg-compiler-plugin", ":kotlin-noarg-compiler-plugin.backend", ":kotlin-noarg-compiler-plugin.cli" +include ":kotlin-swift-export-compiler-plugin", + ":kotlin-swift-export-compiler-plugin.embeddable", + ":kotlin-swift-export-compiler-plugin.backend", + ":kotlin-swift-export-compiler-plugin.cli" + include ":kotlin-sam-with-receiver-compiler-plugin", ":kotlin-sam-with-receiver-compiler-plugin.embeddable", ":kotlin-sam-with-receiver-compiler-plugin.common", @@ -731,6 +736,11 @@ project(':kotlin-noarg-compiler-plugin.k2').projectDir = "$rootDir/plugins/noarg project(':kotlin-noarg-compiler-plugin.backend').projectDir = "$rootDir/plugins/noarg/noarg.backend" as File project(':kotlin-noarg-compiler-plugin.cli').projectDir = "$rootDir/plugins/noarg/noarg.cli" as File +project(':kotlin-swift-export-compiler-plugin').projectDir = "$rootDir/plugins/swift-export" as File +project(':kotlin-swift-export-compiler-plugin.embeddable').projectDir = "$rootDir/plugins/swift-export/swift-export.embeddable" as File +project(':kotlin-swift-export-compiler-plugin.backend').projectDir = "$rootDir/plugins/swift-export/swift-export.backend" as File +project(':kotlin-swift-export-compiler-plugin.cli').projectDir = "$rootDir/plugins/swift-export/swift-export.cli" as File + project(':kotlin-sam-with-receiver-compiler-plugin').projectDir = "$rootDir/plugins/sam-with-receiver" as File project(':kotlin-sam-with-receiver-compiler-plugin.embeddable').projectDir = "$rootDir/plugins/sam-with-receiver/sam-with-receiver.embeddable" as File project(':kotlin-sam-with-receiver-compiler-plugin.common').projectDir = "$rootDir/plugins/sam-with-receiver/sam-with-receiver.common" as File