Skip to content

Commit 1279a73

Browse files
authored
fix(gradle-plugin): architecture folder name missmatch when using SPM and framework path searching (#320)
* update arch folders and test * update test * update comment * refactor linking to own file * update * update * update comment * update comment * update message * draft commit * better logging * add docs per strategy * docs * add FrameworkPathResolver test * update tests * update tests * update tests * formatting * update tests * fix test * add comment to test * dont throw if mac is not host * fix test * improve coverage * detekt * fix test * update * Update CHANGELOG * update test * fix expected architectures * add todo * review feedback * update formatting * formatting
1 parent d3bf403 commit 1279a73

17 files changed

+1388
-280
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixes
6+
7+
- [Gradle Plugin]: Architecture folder name missmatch when using SPM and framework path searching ([#320](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/320))
8+
59
### Dependencies
610

711
- Bump Java SDK from v7.16.0 to v7.18.1 ([#295](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/295), [#299](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/299))

config/detekt/detekt.yml

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ style:
1313
UnusedPrivateMember:
1414
excludes:
1515
- "**/Attachment.kt"
16+
ReturnCount:
17+
active: false
1618
naming:
1719
MatchingDeclarationName:
1820
active: false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package io.sentry.kotlin.multiplatform.gradle
2+
3+
import org.gradle.api.GradleException
4+
import org.gradle.api.logging.Logger
5+
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
6+
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
7+
8+
/**
9+
* Configures Sentry Cocoa framework linking for Apple targets in Kotlin Multiplatform projects.
10+
*
11+
* Resolves framework paths and applies necessary linker options to both test and framework binaries.
12+
*/
13+
class CocoaFrameworkLinker(
14+
private val logger: Logger,
15+
private val pathResolver: FrameworkPathResolver,
16+
private val binaryLinker: FrameworkLinker
17+
) {
18+
fun configure(appleTargets: List<KotlinNativeTarget>) {
19+
appleTargets.forEach { target ->
20+
try {
21+
logger.info(
22+
"Start resolving Sentry Cocoa framework paths for target: ${target.name}"
23+
)
24+
processTarget(target)
25+
logger.lifecycle("Successfully configured Sentry Cocoa framework linking for target: ${target.name}")
26+
} catch (e: FrameworkLinkingException) {
27+
throw FrameworkLinkingException("Failed to configure ${target.name}: ${e.message}", e)
28+
}
29+
}
30+
}
31+
32+
private fun processTarget(target: KotlinNativeTarget) {
33+
val architectures =
34+
target.toSentryFrameworkArchitecture().takeIf { it.isNotEmpty() } ?: run {
35+
logger.warn("Skipping target ${target.name}: Unsupported architecture")
36+
return
37+
}
38+
val paths: FrameworkPaths = pathResolver.resolvePaths(architectures)
39+
binaryLinker.configureBinaries(target.binaries, paths.dynamic, paths.static)
40+
}
41+
}
42+
43+
internal class FrameworkLinkingException(
44+
message: String,
45+
cause: Throwable? = null
46+
) : GradleException(message, cause)
47+
48+
/**
49+
* Transforms a Kotlin Multiplatform target name to possible architecture names found inside
50+
* Sentry's framework directory.
51+
*
52+
* Returns a set of possible architecture names because Sentry Cocoa SDK has changed folder naming
53+
* across different versions. For example:
54+
* - iosArm64 -> [SentryCocoaFrameworkArchitectures.IOS_ARM64]
55+
* - macosArm64 -> [SentryCocoaFrameworkArchitectures.MACOS_ARM64_AND_X64]
56+
* @return Set of possible architecture folder names for the given target.
57+
* Returns empty set if target is not supported.
58+
*/
59+
internal fun KotlinNativeTarget.toSentryFrameworkArchitecture(): Set<String> = buildSet {
60+
when (name) {
61+
"iosSimulatorArm64", "iosX64" -> addAll(SentryCocoaFrameworkArchitectures.IOS_SIMULATOR_AND_X64)
62+
"iosArm64" -> addAll(SentryCocoaFrameworkArchitectures.IOS_ARM64)
63+
"macosArm64", "macosX64" -> addAll(SentryCocoaFrameworkArchitectures.MACOS_ARM64_AND_X64)
64+
"tvosSimulatorArm64", "tvosX64" -> addAll(SentryCocoaFrameworkArchitectures.TVOS_SIMULATOR_AND_X64)
65+
"tvosArm64" -> addAll(SentryCocoaFrameworkArchitectures.TVOS_ARM64)
66+
"watchosArm32", "watchosArm64" -> addAll(SentryCocoaFrameworkArchitectures.WATCHOS_ARM)
67+
"watchosSimulatorArm64", "watchosX64" -> addAll(SentryCocoaFrameworkArchitectures.WATCHOS_SIMULATOR_AND_X64)
68+
}
69+
}
70+
71+
internal object SentryCocoaFrameworkArchitectures {
72+
val IOS_SIMULATOR_AND_X64 = setOf("ios-arm64_x86_64-simulator")
73+
val IOS_ARM64 = setOf("ios-arm64", "ios-arm64_arm64e")
74+
val MACOS_ARM64_AND_X64 = setOf("macos-arm64_x86_64", "macos-arm64_arm64e_x86_64")
75+
val TVOS_SIMULATOR_AND_X64 = setOf("tvos-arm64_x86_64-simulator")
76+
val TVOS_ARM64 = setOf("tvos-arm64", "tvos-arm64_arm64e")
77+
val WATCHOS_ARM = setOf("watchos-arm64_arm64_32_armv7k", "watchos-arm64_arm64_32_arm64e_armv7k")
78+
val WATCHOS_SIMULATOR_AND_X64 = setOf("watchos-arm64_i386_x86_64-simulator")
79+
80+
// Used for tests
81+
val all = setOf(
82+
IOS_SIMULATOR_AND_X64,
83+
IOS_ARM64,
84+
MACOS_ARM64_AND_X64,
85+
TVOS_SIMULATOR_AND_X64,
86+
TVOS_ARM64,
87+
WATCHOS_ARM,
88+
WATCHOS_SIMULATOR_AND_X64
89+
)
90+
}
91+
92+
internal fun KotlinMultiplatformExtension.appleTargets() =
93+
targets.withType(KotlinNativeTarget::class.java).matching {
94+
it.konanTarget.family.isAppleFamily
95+
}

sentry-kotlin-multiplatform-gradle-plugin/src/main/java/io/sentry/kotlin/multiplatform/gradle/DerivedDataPathValueSource.kt

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.sentry.kotlin.multiplatform.gradle
22

3-
import org.gradle.api.GradleException
43
import org.gradle.api.provider.Property
54
import org.gradle.api.provider.ValueSource
65
import org.gradle.api.provider.ValueSourceParameters
@@ -9,8 +8,13 @@ import org.gradle.process.ExecOperations
98
import java.io.ByteArrayOutputStream
109
import javax.inject.Inject
1110

12-
internal abstract class DerivedDataPathValueSource :
13-
ValueSource<String, DerivedDataPathValueSource.Parameters> {
11+
/**
12+
* Provides the derived data path for an Xcode project using the xcodebuild command.
13+
*
14+
* e.g /Users/theusername/Library/Developer/Xcode/DerivedData/iosApp-ddefikekigqzzgcnpfkkdallksmlfpln/
15+
*/
16+
abstract class DerivedDataPathValueSource :
17+
ValueSource<String?, DerivedDataPathValueSource.Parameters> {
1418
interface Parameters : ValueSourceParameters {
1519
@get:Input
1620
val xcodeprojPath: Property<String>
@@ -23,7 +27,7 @@ internal abstract class DerivedDataPathValueSource :
2327
private val buildDirRegex = Regex("BUILD_DIR = (.+)")
2428
}
2529

26-
override fun obtain(): String {
30+
override fun obtain(): String? {
2731
val buildDirOutput = ByteArrayOutputStream()
2832
execOperations.exec {
2933
it.commandLine = listOf(
@@ -37,7 +41,9 @@ internal abstract class DerivedDataPathValueSource :
3741
val buildSettings = buildDirOutput.toString("UTF-8")
3842
val buildDirMatch = buildDirRegex.find(buildSettings)
3943
val buildDir = buildDirMatch?.groupValues?.get(1)
40-
?: throw GradleException("BUILD_DIR not found in xcodebuild output")
44+
if (buildDir == null || buildDir.contains("DerivedData").not()) {
45+
return null
46+
}
4147
return buildDir.replace("/Build/Products", "")
4248
}
4349
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.sentry.kotlin.multiplatform.gradle
2+
3+
import org.gradle.api.logging.Logger
4+
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer
5+
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
6+
import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable
7+
8+
/**
9+
* Responsible for executing the linking.
10+
* This involves configuring and linking binaries to the Sentry Cocoa framework.
11+
*/
12+
class FrameworkLinker(
13+
private val logger: Logger
14+
) {
15+
fun configureBinaries(
16+
binaries: KotlinNativeBinaryContainer,
17+
dynamicPath: String?,
18+
staticPath: String?
19+
) {
20+
binaries.all { binary ->
21+
when (binary) {
22+
is TestExecutable -> linkTestBinary(binary, chooseTestPath(dynamicPath, staticPath))
23+
is Framework -> linkFrameworkBinary(binary, dynamicPath, staticPath)
24+
else -> logger.info("Ignoring binary type: ${binary::class.java.simpleName}")
25+
}
26+
}
27+
}
28+
29+
private fun chooseTestPath(dynamic: String?, static: String?) = when {
30+
dynamic != null -> dynamic
31+
static != null -> static
32+
else -> throw FrameworkLinkingException("No valid framework path found for tests")
33+
}
34+
35+
private fun linkTestBinary(binary: TestExecutable, path: String) {
36+
// Linking in test binaries works with both dynamic and static framework
37+
binary.linkerOpts("-rpath", path, "-F$path")
38+
logger.info("Linked Sentry Cocoa framework to test binary ${binary.name}")
39+
}
40+
41+
private fun linkFrameworkBinary(binary: Framework, dynamicPath: String?, staticPath: String?) {
42+
val (path, type) = when {
43+
binary.isStatic && staticPath != null -> staticPath to "static"
44+
!binary.isStatic && dynamicPath != null -> dynamicPath to "dynamic"
45+
else -> throw FrameworkLinkingException(
46+
"Framework mismatch for ${binary.name}. " +
47+
"Required ${if (binary.isStatic) "static" else "dynamic"} Sentry Cocoa framework not found."
48+
)
49+
}
50+
51+
binary.linkerOpts("-F$path")
52+
logger.info("Linked $type Sentry Cocoa framework to ${binary.name}")
53+
}
54+
}

0 commit comments

Comments
 (0)