Skip to content

Commit f4187ef

Browse files
committed
update arch folders and test
1 parent d3bf403 commit f4187ef

File tree

2 files changed

+160
-57
lines changed

2 files changed

+160
-57
lines changed

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

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -137,47 +137,58 @@ internal fun Project.configureLinkingOptions(linkerExtension: LinkerExtension) {
137137
}
138138

139139
kmpExtension.appleTargets().all { target ->
140-
val frameworkArchitecture = target.toSentryFrameworkArchitecture() ?: run {
140+
// Contains a set of names where one should match the arch name in the framework
141+
// This is needed for backwards compatibility as the arch names have changed throughout different versions of the Cocoa SDK
142+
val frameworkArchitectures = target.toSentryFrameworkArchitecture()
143+
if (frameworkArchitectures.isEmpty()) {
141144
logger.warn("Skipping target ${target.name} - unsupported architecture.")
142145
return@all
143146
}
144147

145-
val dynamicFrameworkPath: String
146-
val staticFrameworkPath: String
148+
var dynamicFrameworkPath: String? = null
149+
var staticFrameworkPath: String? = null
147150

148151
if (frameworkPath?.isNotEmpty() == true) {
149152
dynamicFrameworkPath = frameworkPath
150153
staticFrameworkPath = frameworkPath
151154
} else {
152-
@Suppress("MaxLineLength")
153-
dynamicFrameworkPath =
154-
"$derivedDataPath/SourcePackages/artifacts/sentry-cocoa/Sentry-Dynamic/Sentry-Dynamic.xcframework/$frameworkArchitecture"
155-
@Suppress("MaxLineLength")
156-
staticFrameworkPath =
157-
"$derivedDataPath/SourcePackages/artifacts/sentry-cocoa/Sentry/Sentry.xcframework/$frameworkArchitecture"
158-
}
155+
frameworkArchitectures.forEach {
156+
val dynamicPath =
157+
"$derivedDataPath/SourcePackages/artifacts/sentry-cocoa/Sentry-Dynamic/Sentry-Dynamic.xcframework/$it"
158+
logger.info("Looking for dynamic framework at $dynamicPath")
159+
if (File(dynamicPath).exists()) {
160+
logger.info("Found dynamic framework at $dynamicPath")
161+
dynamicFrameworkPath = dynamicPath
162+
return@forEach
163+
}
159164

160-
val dynamicFrameworkExists = File(dynamicFrameworkPath).exists()
161-
val staticFrameworkExists = File(staticFrameworkPath).exists()
165+
val staticPath = "$derivedDataPath/SourcePackages/artifacts/sentry-cocoa/Sentry/Sentry.xcframework/$it"
166+
logger.info("Looking for dynamic framework at $dynamicPath")
167+
if (File(staticPath).exists()) {
168+
logger.info("Found static framework at $staticPath")
169+
staticFrameworkPath = staticPath
170+
return@forEach
171+
}
172+
}
173+
}
162174

163-
if (!dynamicFrameworkExists && !staticFrameworkExists) {
175+
if (staticFrameworkPath == null && dynamicFrameworkPath == null) {
164176
throw GradleException(
165-
"Sentry Cocoa Framework not found at $dynamicFrameworkPath or $staticFrameworkPath"
177+
"Sentry Cocoa Framework not found. Make sure the Sentry Cocoa SDK is installed with SPM in your Xcode project."
166178
)
167179
}
168180

169181
target.binaries.all binaries@{ binary ->
170182
if (binary is TestExecutable) {
171183
// both dynamic and static frameworks will work for tests
172-
val finalFrameworkPath =
173-
if (dynamicFrameworkExists) dynamicFrameworkPath else staticFrameworkPath
174-
binary.linkerOpts("-rpath", finalFrameworkPath, "-F$finalFrameworkPath")
184+
val path = (dynamicFrameworkPath ?: staticFrameworkPath)!!
185+
binary.linkerOpts("-rpath", path, "-F$path")
175186
}
176187

177188
if (binary is Framework) {
178189
val finalFrameworkPath = when {
179-
binary.isStatic && staticFrameworkExists -> staticFrameworkPath
180-
!binary.isStatic && dynamicFrameworkExists -> dynamicFrameworkPath
190+
binary.isStatic && staticFrameworkPath != null -> staticFrameworkPath
191+
!binary.isStatic && dynamicFrameworkPath != null -> dynamicFrameworkPath
181192
else -> {
182193
logger.warn("Linking to framework failed, no sentry framework found for target ${target.name}")
183194
return@binaries
@@ -191,19 +202,44 @@ internal fun Project.configureLinkingOptions(linkerExtension: LinkerExtension) {
191202
}
192203

193204
/**
194-
* Transforms a Kotlin Multiplatform target name to the architecture name that is found inside
205+
* Transforms a Kotlin Multiplatform target name to possible architecture names found inside
195206
* Sentry's framework directory.
207+
*
208+
* Returns a set of possible architecture names because Sentry Cocoa SDK has changed folder naming
209+
* across different versions. For example:
210+
* - iosArm64 -> ["ios-arm64", "ios-arm64_arm64e"]
211+
* - macosArm64 -> ["macos-arm64_x86_64", "macos-arm64_arm64e_x86_64"]
212+
* *
213+
* @return Set of possible architecture folder names for the given target. Returns empty set if target is not supported.
196214
*/
197-
internal fun KotlinNativeTarget.toSentryFrameworkArchitecture(): String? {
198-
return when (name) {
199-
"iosSimulatorArm64", "iosX64" -> "ios-arm64_x86_64-simulator"
200-
"iosArm64" -> "ios-arm64"
201-
"macosArm64", "macosX64" -> "macos-arm64_x86_64"
202-
"tvosSimulatorArm64", "tvosX64" -> "tvos-arm64_x86_64-simulator"
203-
"tvosArm64" -> "tvos-arm64"
204-
"watchosArm32", "watchosArm64" -> "watchos-arm64_arm64_32_armv7k"
205-
"watchosSimulatorArm64", "watchosX64" -> "watchos-arm64_i386_x86_64-simulator"
206-
else -> null
215+
internal fun KotlinNativeTarget.toSentryFrameworkArchitecture(): Set<String> = buildSet {
216+
when (name) {
217+
"iosSimulatorArm64", "iosX64" -> add("ios-arm64_x86_64-simulator")
218+
"iosArm64" -> {
219+
add("ios-arm64")
220+
add("ios-arm64_arm64e")
221+
}
222+
"macosArm64", "macosX64" -> {
223+
add("macos-arm64_x86_64")
224+
add("macos-arm64_arm64e_x86_64")
225+
}
226+
"tvosSimulatorArm64", "tvosX64" -> {
227+
add("tvos-arm64_x86_64-simulator")
228+
add("tvos-arm64_x86_64-simulator")
229+
}
230+
"tvosArm64" -> {
231+
add("tvos-arm64")
232+
add("tvos-arm64_arm64e")
233+
}
234+
"watchosArm32", "watchosArm64" -> {
235+
add("watchos-arm64_arm64_32_armv7k")
236+
add("watchos-arm64_arm64_32_arm64e_armv7k")
237+
}
238+
"watchosSimulatorArm64", "watchosX64" -> {
239+
add("watchos-arm64_i386_x86_64-simulator")
240+
add("watchos-arm64_i386_x86_64-simulator")
241+
}
242+
else -> emptySet<String>()
207243
}
208244
}
209245

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

3-
import io.mockk.every
4-
import io.mockk.mockk
5-
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
6-
import org.junit.jupiter.api.Assertions.assertEquals
3+
import org.gradle.testfixtures.ProjectBuilder
4+
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
75
import org.junit.jupiter.params.ParameterizedTest
86
import org.junit.jupiter.params.provider.Arguments
97
import org.junit.jupiter.params.provider.MethodSource
8+
import java.io.File
9+
import java.net.URL
10+
import java.nio.file.Files
11+
import java.nio.file.StandardCopyOption
12+
import java.util.zip.ZipFile
1013

1114
class SentryFrameworkArchitectureTest {
1215
companion object {
1316
@JvmStatic
14-
fun architectureData(): List<Arguments> = listOf(
15-
Arguments.of("iosSimulatorArm64", "ios-arm64_x86_64-simulator"),
16-
Arguments.of("iosX64", "ios-arm64_x86_64-simulator"),
17-
Arguments.of("iosArm64", "ios-arm64"),
18-
Arguments.of("macosArm64", "macos-arm64_x86_64"),
19-
Arguments.of("macosX64", "macos-arm64_x86_64"),
20-
Arguments.of("tvosSimulatorArm64", "tvos-arm64_x86_64-simulator"),
21-
Arguments.of("tvosX64", "tvos-arm64_x86_64-simulator"),
22-
Arguments.of("tvosArm64", "tvos-arm64"),
23-
Arguments.of("watchosArm32", "watchos-arm64_arm64_32_armv7k"),
24-
Arguments.of("watchosArm64", "watchos-arm64_arm64_32_armv7k"),
25-
Arguments.of("watchosSimulatorArm64", "watchos-arm64_i386_x86_64-simulator"),
26-
Arguments.of("watchosX64", "watchos-arm64_i386_x86_64-simulator"),
27-
Arguments.of("unsupportedTarget", null)
17+
fun cocoaVersions(): List<Arguments> = listOf(
18+
Arguments.of("8.37.0"),
19+
Arguments.of("8.38.0"),
20+
Arguments.of("latest")
2821
)
2922
}
3023

31-
@ParameterizedTest(name = "Target {0} should return {1}")
32-
@MethodSource("architectureData")
33-
fun `toSentryFrameworkArchitecture returns correct architecture for all targets`(
34-
targetName: String,
35-
expectedArchitecture: String?
24+
@ParameterizedTest(name = "Test SPM compatibility with Cocoa Version {0}")
25+
@MethodSource("cocoaVersions")
26+
fun `finds arch folders across different cocoa versions`(
27+
cocoaVersion: String
3628
) {
37-
val target = mockk<KotlinNativeTarget>()
38-
every { target.name } returns targetName
29+
val project = ProjectBuilder.builder().build()
30+
project.pluginManager.apply {
31+
apply("org.jetbrains.kotlin.multiplatform")
32+
apply("io.sentry.kotlin.multiplatform.gradle")
33+
}
3934

40-
val result = target.toSentryFrameworkArchitecture()
35+
val kmpExtension = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension
36+
kmpExtension.apply {
37+
listOf(
38+
iosX64(),
39+
iosArm64(),
40+
iosSimulatorArm64(),
41+
macosArm64(),
42+
macosX64(),
43+
watchosX64(),
44+
watchosArm32(),
45+
watchosSimulatorArm64(),
46+
tvosX64(),
47+
tvosArm64(),
48+
tvosSimulatorArm64()
49+
).forEach {
50+
it.binaries.framework {
51+
baseName = "shared"
52+
isStatic = false
53+
}
54+
}
55+
}
56+
val frameworkDir = downloadAndUnzip(cocoaVersion)
57+
val xcFramework = File(frameworkDir, "Sentry.xcframework")
4158

42-
assertEquals(expectedArchitecture, result)
59+
val downloadedArchNames =
60+
xcFramework.listFiles()?.map { it.name } ?: throw IllegalStateException("No archs found")
61+
62+
kmpExtension.appleTargets().forEach {
63+
val mappedArchNames = it.toSentryFrameworkArchitecture()
64+
val foundMatch = mappedArchNames.any { mappedArchName ->
65+
downloadedArchNames.contains(mappedArchName)
66+
}
67+
68+
assert(foundMatch) {
69+
"Expected to find one of $mappedArchNames in $xcFramework for target ${it.name}.\nFound instead: ${xcFramework.listFiles()
70+
?.map { file -> file.name }}"
71+
}
72+
}
73+
}
74+
75+
private fun downloadAndUnzip(cocoaVersion: String): File {
76+
val tempDir = Files.createTempDirectory("sentry-cocoa-test").toFile()
77+
tempDir.deleteOnExit()
78+
79+
val targetFile = tempDir.resolve("Sentry.xcframework.zip")
80+
val downloadLink =
81+
if (cocoaVersion == "latest") "https://github.com/getsentry/sentry-cocoa/releases/latest/download/Sentry.xcframework.zip" else "https://github.com/getsentry/sentry-cocoa/releases/download/$cocoaVersion/Sentry.xcframework.zip"
82+
83+
val url = URL(downloadLink)
84+
url.openStream().use { input ->
85+
Files.copy(
86+
input,
87+
targetFile.toPath(),
88+
StandardCopyOption.REPLACE_EXISTING
89+
)
90+
}
91+
92+
ZipFile(targetFile).use { zip ->
93+
zip.entries().asSequence().forEach { entry ->
94+
val entryFile = File(tempDir, entry.name)
95+
if (entry.isDirectory) {
96+
entryFile.mkdirs()
97+
} else {
98+
entryFile.parentFile?.mkdirs()
99+
zip.getInputStream(entry).use { input ->
100+
entryFile.outputStream().use { output ->
101+
input.copyTo(output)
102+
}
103+
}
104+
}
105+
}
106+
}
107+
108+
targetFile.delete()
109+
return tempDir
43110
}
44111
}

0 commit comments

Comments
 (0)