diff --git a/SKIE/common/configuration/declaration/src/commonMain/kotlin/co/touchlab/skie/configuration/SkieConfigurationFlag.kt b/SKIE/common/configuration/declaration/src/commonMain/kotlin/co/touchlab/skie/configuration/SkieConfigurationFlag.kt index 7cd6a2ccb..dc34a288d 100644 --- a/SKIE/common/configuration/declaration/src/commonMain/kotlin/co/touchlab/skie/configuration/SkieConfigurationFlag.kt +++ b/SKIE/common/configuration/declaration/src/commonMain/kotlin/co/touchlab/skie/configuration/SkieConfigurationFlag.kt @@ -16,6 +16,7 @@ enum class SkieConfigurationFlag { Build_ParallelSkieCompilation, Build_ConcurrentSkieCompilation, Build_NoClangModuleBreadcrumbsInStaticFramework, + Build_RelativeSourcePathsInDebugSymbols, Migration_WildcardExport, Migration_AnyMethodsAsFunctions, diff --git a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/phases/swift/CompileSwiftPhase.kt b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/phases/swift/CompileSwiftPhase.kt index 62bb4d5eb..739ea5e13 100644 --- a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/phases/swift/CompileSwiftPhase.kt +++ b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/phases/swift/CompileSwiftPhase.kt @@ -8,6 +8,7 @@ import co.touchlab.skie.sir.element.SirCompilableFile import co.touchlab.skie.util.Command import kotlin.io.path.absolutePathString import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.pathString class CompileSwiftPhase( context: SirPhase.Context, @@ -31,6 +32,7 @@ class CompileSwiftPhase( private val isConcurrentSkieCompilationEnabled = SkieConfigurationFlag.Build_ConcurrentSkieCompilation in globalConfiguration.enabledFlags private val noClangModuleBreadcrumbsInStaticFramework = SkieConfigurationFlag.Build_NoClangModuleBreadcrumbsInStaticFramework in globalConfiguration.enabledFlags + private val isRelativeSourcePathsInDebugSymbolsEnabled = SkieConfigurationFlag.Build_RelativeSourcePathsInDebugSymbols in globalConfiguration.enabledFlags context(SirPhase.Context) override suspend fun execute() { @@ -48,7 +50,7 @@ class CompileSwiftPhase( } private fun createSwiftFileList(compilableFiles: List) { - val content = compilableFiles.joinToString("\n") { "'${it.absolutePath.absolutePathString()}'" } + val content = compilableFiles.joinToString("\n") { "'${it.relativePath.pathString}'" } swiftFileList.writeText(content) } @@ -71,10 +73,10 @@ class CompileSwiftPhase( """.trimIndent() val body = compilableFiles.joinToString(",\n") { compilableFile -> - val sourceFileName = compilableFile.absolutePath.nameWithoutExtension + val sourceFileName = compilableFile.relativePath.nameWithoutExtension """ - "${compilableFile.absolutePath}": { + "${compilableFile.relativePath.pathString}": { "object": "${objectFileProvider.getOrCreate(compilableFile).absolutePath.absolutePathString()}", "dependencies": "${objectFiles.dependencies(sourceFileName).absolutePath}", "swift-dependencies": "${objectFiles.swiftDependencies(sourceFileName).absolutePath}", @@ -142,10 +144,10 @@ class CompileSwiftPhase( } } +"-output-file-map" - +outputFileMap.absolutePath + +outputFileMap +"-g" +"-module-cache-path" - +skieBuildDirectory.cache.swiftModules.directory.absolutePath + +skieBuildDirectory.cache.swiftModules.directory +"-swift-version" +swiftCompilerConfiguration.swiftVersion +parallelizationArgument @@ -153,10 +155,16 @@ class CompileSwiftPhase( +swiftCompilerConfiguration.absoluteTargetSysRootPath +"-target" +swiftCompilerConfiguration.targetTriple.withOsVersion(swiftCompilerConfiguration.osVersionMin).toString() + + if (isRelativeSourcePathsInDebugSymbolsEnabled) { + +"-file-compilation-dir" + +"." + } + +swiftCompilerConfiguration.freeCompilerArgs +"@${swiftFileList.absolutePath}" - workingDirectory = objectFiles.directory + workingDirectory = skieBuildDirectory.swift.directory execute(logFile = skieBuildDirectory.debug.logs.swiftc) } diff --git a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/SirFileProvider.kt b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/SirFileProvider.kt index cfd5bd1a5..01b2c9f13 100644 --- a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/SirFileProvider.kt +++ b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/SirFileProvider.kt @@ -56,7 +56,14 @@ class SirFileProvider( absolutePath.writeTextIfDifferent(sourceFile.content) - return SirCompilableFile(sourceFile.module, absolutePath, sourceFile) + val relativePath = skieBuildDirectory.swift.path.relativize(absolutePath) + + return SirCompilableFile( + module = sourceFile.module, + absolutePath = absolutePath, + relativePath = relativePath, + originFile = sourceFile, + ) } fun loadCompilableFile(path: Path): SirCompilableFile { @@ -74,7 +81,14 @@ class SirFileProvider( "Custom source file must have the swift extension. Was: $absolutePath." } - return SirCompilableFile(skieModule, absolutePath, null) + val relativePath = skieBuildDirectory.swift.path.relativize(absolutePath) + + return SirCompilableFile( + module = skieModule, + absolutePath = absolutePath, + relativePath = relativePath, + originFile = null + ) } private val Path.asCacheKey: String diff --git a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/compilation/ObjectFileProvider.kt b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/compilation/ObjectFileProvider.kt index cb09abc92..f4868213d 100644 --- a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/compilation/ObjectFileProvider.kt +++ b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/compilation/ObjectFileProvider.kt @@ -19,7 +19,7 @@ class ObjectFileProvider( fun getOrCreate(compilableFile: SirCompilableFile): ObjectFile = compilableFilesCache.getOrPut(compilableFile) { - val objectFilePath = skieBuildDirectory.swiftCompiler.objectFiles.objectFile(compilableFile.absolutePath.nameWithoutExtension).toPath() + val objectFilePath = skieBuildDirectory.swiftCompiler.objectFiles.objectFile(compilableFile.relativePath.nameWithoutExtension).toPath() ObjectFile(objectFilePath) } diff --git a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/element/SirCompilableFile.kt b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/element/SirCompilableFile.kt index 5328da301..7965b7ab9 100644 --- a/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/element/SirCompilableFile.kt +++ b/SKIE/kotlin-compiler/core/src/commonMain/kotlin/co/touchlab/skie/sir/element/SirCompilableFile.kt @@ -6,6 +6,8 @@ import java.nio.file.Path class SirCompilableFile( override val module: SirModule.Skie, val absolutePath: Path, + // Relative to the SKIE Swift build directory + val relativePath: Path, val originFile: SirSourceFile?, ) : SirFile { diff --git a/SKIE/kotlin-compiler/linker-plugin/src/kgp_1.9.0..2.1.0/kotlin/co/touchlab/skie/entrypoint/CodegenPhaseInterceptor.kt b/SKIE/kotlin-compiler/linker-plugin/src/kgp_1.9.0..2.1.0/kotlin/co/touchlab/skie/entrypoint/CodegenPhaseInterceptor.kt new file mode 100644 index 000000000..e9ab3ddd6 --- /dev/null +++ b/SKIE/kotlin-compiler/linker-plugin/src/kgp_1.9.0..2.1.0/kotlin/co/touchlab/skie/entrypoint/CodegenPhaseInterceptor.kt @@ -0,0 +1,50 @@ +@file:Suppress("invisible_reference", "invisible_member") + +package co.touchlab.skie.entrypoint + +import co.touchlab.skie.compilerinject.compilerplugin.mainSkieContext +import co.touchlab.skie.compilerinject.interceptor.PhaseInterceptor +import co.touchlab.skie.configuration.SkieConfigurationFlag +import org.jetbrains.kotlin.backend.konan.KonanConfigKeys +import org.jetbrains.kotlin.backend.konan.NativeGenerationState +import org.jetbrains.kotlin.backend.konan.driver.phases.CodegenInput +import org.jetbrains.kotlin.backend.konan.driver.phases.CodegenPhase +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity + +internal class CodegenPhaseInterceptor : PhaseInterceptor { + override fun getInterceptedPhase(): Any = CodegenPhase + + override fun intercept(context: NativeGenerationState, input: CodegenInput, next: (NativeGenerationState, CodegenInput) -> Unit) { + val mainSkieContext = context.config.configuration.mainSkieContext + with(mainSkieContext) { + if (SkieConfigurationFlag.Build_RelativeSourcePathsInDebugSymbols.isEnabled) { + workaroundRelativeDebugPrefixMapBug(context) + } + } + + next(context, input) + } + + private fun workaroundRelativeDebugPrefixMapBug(context: NativeGenerationState) { + if (context.hasDebugInfo()) { + context.context.messageCollector.report( + severity = CompilerMessageSeverity.ERROR, + message = "NativeGenerationState.debugInfo was initialized before debug-prefix-map workaround was applied! " + + "Please disable the debug-prefix-map SKIE feature and report this issue to the SKIE GitHub at https://github.com/touchlab/SKIE", + ) + } else { + /* + * This piece of code removes + */ + val existingMap = context.config.configuration.getMap(KonanConfigKeys.DEBUG_PREFIX_MAP) + context.config.configuration.put(KonanConfigKeys.DEBUG_PREFIX_MAP, emptyMap()) + + // Touch the `debugInfo` to create it while the `DEBUG_PREFIX_MAP` is empty. + @Suppress("UNUSED_VARIABLE") + val debugInfo = context.debugInfo + + // Set the `DEBUG_PREFIX_MAP` back to original value so the .kt source files can get remapped correctly. + context.config.configuration.put(KonanConfigKeys.DEBUG_PREFIX_MAP, existingMap) + } + } +} diff --git a/SKIE/kotlin-compiler/linker-plugin/src/kgp_1.9.0..2.1.0/resources/META-INF/services/co.touchlab.skie.compilerinject.interceptor.PhaseInterceptor b/SKIE/kotlin-compiler/linker-plugin/src/kgp_1.9.0..2.1.0/resources/META-INF/services/co.touchlab.skie.compilerinject.interceptor.PhaseInterceptor index 2f8fbfc93..6442f1dc5 100644 --- a/SKIE/kotlin-compiler/linker-plugin/src/kgp_1.9.0..2.1.0/resources/META-INF/services/co.touchlab.skie.compilerinject.interceptor.PhaseInterceptor +++ b/SKIE/kotlin-compiler/linker-plugin/src/kgp_1.9.0..2.1.0/resources/META-INF/services/co.touchlab.skie.compilerinject.interceptor.PhaseInterceptor @@ -1,3 +1,4 @@ co.touchlab.skie.entrypoint.CreateObjCExportCodeSpecPhaseInterceptor co.touchlab.skie.entrypoint.LinkerPhaseInterceptor co.touchlab.skie.entrypoint.ProduceObjCExportInterfacePhaseInterceptor +co.touchlab.skie.entrypoint.CodegenPhaseInterceptor diff --git a/SKIE/skie-gradle/plugin-api/src/main/kotlin/co/touchlab/skie/plugin/configuration/SkieBuildConfiguration.kt b/SKIE/skie-gradle/plugin-api/src/main/kotlin/co/touchlab/skie/plugin/configuration/SkieBuildConfiguration.kt index 9ccaa079b..ffd56a12a 100644 --- a/SKIE/skie-gradle/plugin-api/src/main/kotlin/co/touchlab/skie/plugin/configuration/SkieBuildConfiguration.kt +++ b/SKIE/skie-gradle/plugin-api/src/main/kotlin/co/touchlab/skie/plugin/configuration/SkieBuildConfiguration.kt @@ -42,6 +42,19 @@ abstract class SkieBuildConfiguration @Inject constructor(objects: ObjectFactory val enableConcurrentSkieCompilation: Property = objects.property(Boolean::class.java).convention(true) val enableParallelSkieCompilation: Property = objects.property(Boolean::class.java).convention(true) + /** + * SKIE can configure the Kotlin/Native compiler to make source file paths in the final binary relative. + * When enabled, + * SKIE will configure `-Xdebug-prefix-map` to replace the value of `rootProject.projectDir.absolutePath` with `.`, + * and then workaround a bug in Kotlin/Native compiler that'd otherwise result in the binary missing links to these source files. + * + * Doing this enables debugging of Kotlin sources built on a different machine, + * which compiles the Kotlin code from a different path. + * With `xcode-kotlin` (https://github.com/touchlab/xcode-kotlin) you can debug Kotlin code + * that's been compiled into a binary .framework on your CI and then distributed through SwiftPM or CocoaPods. + */ + val enableRelativeSourcePathsInDebugSymbols: Property = objects.property(Boolean::class.java).convention(false) + /** * Additional Swift compiler arguments that will be passed to the Swift compiler. */ @@ -58,6 +71,7 @@ abstract class SkieBuildConfiguration @Inject constructor(objects: ObjectFactory fun produceDistributableFramework() { enableSwiftLibraryEvolution.set(true) noClangModuleBreadcrumbsInStaticFrameworks.set(true) + enableRelativeSourcePathsInDebugSymbols.set(true) } internal fun buildConfigurationFlags(): Set = @@ -67,6 +81,7 @@ abstract class SkieBuildConfiguration @Inject constructor(objects: ObjectFactory SkieConfigurationFlag.Build_ParallelSwiftCompilation takeIf enableParallelSwiftCompilation, SkieConfigurationFlag.Build_ConcurrentSkieCompilation takeIf enableConcurrentSkieCompilation, SkieConfigurationFlag.Build_ParallelSkieCompilation takeIf enableParallelSkieCompilation, + SkieConfigurationFlag.Build_RelativeSourcePathsInDebugSymbols takeIf enableRelativeSourcePathsInDebugSymbols, ) internal fun buildItems(): Map = mapOf( diff --git a/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/SkieGradlePluginApplier.kt b/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/SkieGradlePluginApplier.kt index c7602a16b..fe341c654 100644 --- a/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/SkieGradlePluginApplier.kt +++ b/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/SkieGradlePluginApplier.kt @@ -12,6 +12,7 @@ import co.touchlab.skie.plugin.defaultarguments.disableCachingIfNeeded import co.touchlab.skie.plugin.dependencies.SkieCompilerPluginDependencyProvider import co.touchlab.skie.plugin.directory.SkieDirectoriesManager import co.touchlab.skie.plugin.fatframework.FatFrameworkConfigurator +import co.touchlab.skie.plugin.relativepaths.configureDebugPrefixMap import co.touchlab.skie.plugin.subplugin.SkieSubPluginManager import co.touchlab.skie.plugin.switflink.SwiftBundlingConfigurator import co.touchlab.skie.plugin.switflink.SwiftUnpackingConfigurator @@ -65,6 +66,7 @@ object SkieGradlePluginApplier { GradleAnalyticsManager(project).configureAnalytics(this) configureMinOsVersionIfNeeded() + configureDebugPrefixMap() CreateSkieConfigurationTask.registerTask(this) diff --git a/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/relativepaths/ConfigureDebugPrefixMap.kt b/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/relativepaths/ConfigureDebugPrefixMap.kt new file mode 100644 index 000000000..c0cc7f748 --- /dev/null +++ b/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/relativepaths/ConfigureDebugPrefixMap.kt @@ -0,0 +1,13 @@ +package co.touchlab.skie.plugin.relativepaths + +import co.touchlab.skie.plugin.SkieTarget + +fun SkieTarget.configureDebugPrefixMap() { + if (!project.isRelativeSourcePathsPreviewEnabled) { + return + } + + addFreeCompilerArgs( + "-Xdebug-prefix-map=${project.rootDir.absolutePath}=." + ) +} diff --git a/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/relativepaths/IsRelativeSourcePathsPreviewEnabled.kt b/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/relativepaths/IsRelativeSourcePathsPreviewEnabled.kt new file mode 100644 index 000000000..3055f89bf --- /dev/null +++ b/SKIE/skie-gradle/plugin-impl/src/main/kotlin/co/touchlab/skie/plugin/relativepaths/IsRelativeSourcePathsPreviewEnabled.kt @@ -0,0 +1,7 @@ +package co.touchlab.skie.plugin.relativepaths + +import co.touchlab.skie.plugin.configuration.skieExtension +import org.gradle.api.Project + +val Project.isRelativeSourcePathsPreviewEnabled: Boolean + get() = project.skieExtension.build.enableRelativeSourcePathsInDebugSymbols.get() diff --git a/dev-support/build.gradle.kts b/dev-support/build.gradle.kts index 68d230b8f..8a3e9129e 100644 --- a/dev-support/build.gradle.kts +++ b/dev-support/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("dev.root") - kotlin("multiplatform") version "2.1.0-RC2" apply false + kotlin("multiplatform") version "2.1.0" apply false } buildscript { diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/suite/gradle/relativepaths/RelativeSourcePathsTests.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/suite/gradle/relativepaths/RelativeSourcePathsTests.kt new file mode 100644 index 000000000..2f88813d3 --- /dev/null +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/suite/gradle/relativepaths/RelativeSourcePathsTests.kt @@ -0,0 +1,84 @@ +package co.touchlab.skie.test.suite.gradle.relativepaths + +import co.touchlab.skie.test.annotation.MatrixTest +import co.touchlab.skie.test.annotation.filter.Smoke +import co.touchlab.skie.test.annotation.type.GradleTests +import co.touchlab.skie.test.base.BaseGradleTests +import co.touchlab.skie.test.runner.BuildConfiguration +import co.touchlab.skie.test.template.Templates +import co.touchlab.skie.test.util.KotlinTarget +import co.touchlab.skie.test.util.KotlinVersion +import co.touchlab.skie.test.util.LinkMode +import co.touchlab.skie.test.util.execute +import kotlin.test.assertContains +import kotlin.test.assertEquals + +@Smoke +@GradleTests +class RelativeSourcePathsTests: BaseGradleTests() { + + @MatrixTest + fun `basic`( + kotlinVersion: KotlinVersion, + linkMode: LinkMode, + configuration: BuildConfiguration, + ) { + rootBuildFile(kotlinVersion) { + kotlin { + allIos() + + registerNativeFrameworks( + kotlinVersion = kotlinVersion, + buildConfiguration = configuration, + linkMode = linkMode, + ) + } + + +""" + skie { + build { + enableRelativeSourcePathsInDebugSymbols = true + } + } + """.trimIndent() + } + + copyToCommonMain(Templates.basic) + + runGradle() + + KotlinTarget.Native.Ios.targets.forEach { target -> + val frameworkDir = builtFrameworkParentDir(target, configuration, isArtifactDsl = false) + val dwarfContainingBinary = when (linkMode) { + LinkMode.Dynamic -> "$frameworkDir/gradle_test.framework.dSYM/Contents/Resources/DWARF/gradle_test" + LinkMode.Static -> "$frameworkDir/gradle_test.framework/gradle_test" + } + + val debugSources = debugSourcesOf(dwarfContainingBinary) + val expectedSources = listOf( + "./src/commonMain/kotlin/templates/basic/BasicSkieFeatures.kt", + "./bundled/gradle-test/bundled.gradle-test.BundledSwift.swift", + "./generated/GradleTest/GradleTest.BasicEnum.swift", + "./generated/GradleTest/GradleTest.SealedClass.swift", + "./generated/GradleTest/GradleTest.SealedInterface.swift", + "./generated/Skie/Skie.Namespace.swift", + ) + expectedSources.forEach { + assertContains(debugSources, it) + } + } + } + + private fun debugSourcesOf(dwarfContainingBinary: String): Set { + val command = listOf( + "/usr/bin/dwarfdump", + "--show-sources", + dwarfContainingBinary, + ) + + val result = command.joinToString(" ").execute(testProjectDir) + assertEquals(0, result.exitCode) + return result.stdOut.lines().toSet() + } + +}