Skip to content

Commit

Permalink
Add support for relative file paths in debug symbols.
Browse files Browse the repository at this point in the history
  • Loading branch information
TadeasKriz committed Dec 9, 2024
1 parent d893073 commit e498c10
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum class SkieConfigurationFlag {
Build_ParallelSkieCompilation,
Build_ConcurrentSkieCompilation,
Build_NoClangModuleBreadcrumbsInStaticFramework,
Build_RelativeSourcePathsInDebugSymbols,

Migration_WildcardExport,
Migration_AnyMethodsAsFunctions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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() {
Expand All @@ -48,7 +50,7 @@ class CompileSwiftPhase(
}

private fun createSwiftFileList(compilableFiles: List<SirCompilableFile>) {
val content = compilableFiles.joinToString("\n") { "'${it.absolutePath.absolutePathString()}'" }
val content = compilableFiles.joinToString("\n") { "'${it.relativePath.pathString}'" }

swiftFileList.writeText(content)
}
Expand All @@ -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}",
Expand Down Expand Up @@ -142,21 +144,27 @@ 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
+"-sdk"
+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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
@@ -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<NativeGenerationState, CodegenInput, Unit> {
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)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
co.touchlab.skie.entrypoint.CreateObjCExportCodeSpecPhaseInterceptor
co.touchlab.skie.entrypoint.LinkerPhaseInterceptor
co.touchlab.skie.entrypoint.ProduceObjCExportInterfacePhaseInterceptor
co.touchlab.skie.entrypoint.CodegenPhaseInterceptor
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ abstract class SkieBuildConfiguration @Inject constructor(objects: ObjectFactory
val enableConcurrentSkieCompilation: Property<Boolean> = objects.property(Boolean::class.java).convention(true)
val enableParallelSkieCompilation: Property<Boolean> = 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<Boolean> = objects.property(Boolean::class.java).convention(false)

/**
* Additional Swift compiler arguments that will be passed to the Swift compiler.
*/
Expand All @@ -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<SkieConfigurationFlag> =
Expand All @@ -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<String, String?> = mapOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,6 +66,7 @@ object SkieGradlePluginApplier {
GradleAnalyticsManager(project).configureAnalytics(this)

configureMinOsVersionIfNeeded()
configureDebugPrefixMap()

CreateSkieConfigurationTask.registerTask(this)

Expand Down
Original file line number Diff line number Diff line change
@@ -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}=."
)
}
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion dev-support/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> {
val command = listOf(
"/usr/bin/dwarfdump",
"--show-sources",
dwarfContainingBinary,
)

val result = command.joinToString(" ").execute(testProjectDir)
assertEquals(0, result.exitCode)
return result.stdOut.lines().toSet()
}

}

0 comments on commit e498c10

Please sign in to comment.