1+ package io.sentry.kotlin.multiplatform.gradle
2+
3+ import org.gradle.api.Project
4+ import java.io.File
5+
6+ data class FrameworkPaths (
7+ val dynamic : String? = null ,
8+ val static : String? = null
9+ ) {
10+ companion object {
11+ val NONE = FrameworkPaths (null , null )
12+
13+ fun createValidated (
14+ dynamicBasePath : String? = null,
15+ staticBasePath : String? = null,
16+ architectures : Set <String >,
17+ pathExists : (String ) -> Boolean = { path -> File (path).exists() }
18+ ): FrameworkPaths {
19+ // Find first valid dynamic path
20+ val dynamicPath = dynamicBasePath?.let { basePath ->
21+ architectures.firstNotNullOfOrNull { arch ->
22+ val path = " $basePath /$arch "
23+ path.takeIf { pathExists(it) }
24+ }
25+ }
26+
27+ // Find first valid static path
28+ val staticPath = staticBasePath?.let { basePath ->
29+ architectures.firstNotNullOfOrNull { arch ->
30+ val path = " $basePath /$arch "
31+ path.takeIf { pathExists(it) }
32+ }
33+ }
34+
35+ return when {
36+ dynamicPath != null && staticPath != null ->
37+ FrameworkPaths (dynamic = dynamicPath, static = staticPath)
38+
39+ dynamicPath != null ->
40+ FrameworkPaths (dynamic = dynamicPath)
41+
42+ staticPath != null ->
43+ FrameworkPaths (static = staticPath)
44+
45+ else ->
46+ NONE
47+ }
48+ }
49+ }
50+ }
51+
52+ sealed interface FrameworkResolutionStrategy {
53+ fun resolvePaths (
54+ architectures : Set <String >,
55+ ): FrameworkPaths
56+ }
57+
58+ /* *
59+ * Handles the custom framework paths set by the user. This should generally be executed first.
60+ */
61+ class CustomPathStrategy (
62+ private val project : Project ,
63+ ) : FrameworkResolutionStrategy {
64+ private val linker: LinkerExtension = project.extensions.getByType(LinkerExtension ::class .java)
65+
66+ // In this function we don't really distinguish between static and dynamic framework
67+ // We trust that the user knows the distinction if they purposefully override the framework path
68+ override fun resolvePaths (architectures : Set <String >): FrameworkPaths {
69+ return linker.frameworkPath.orNull?.takeIf { it.isNotEmpty() }?.let { basePath ->
70+ when {
71+ basePath.endsWith(" Sentry.xcframework" ) -> FrameworkPaths .createValidated(
72+ staticBasePath = basePath,
73+ architectures = architectures
74+ )
75+
76+ basePath.endsWith(" Sentry-Dynamic.xcframework" ) -> FrameworkPaths .createValidated(
77+ dynamicBasePath = basePath,
78+ architectures = architectures
79+ )
80+
81+ else -> null
82+ }
83+ } ? : FrameworkPaths .NONE
84+ }
85+ }
86+
87+ class DerivedDataStrategy (
88+ private val project : Project ,
89+ ) : FrameworkResolutionStrategy {
90+ private val linker: LinkerExtension = project.extensions.getByType(LinkerExtension ::class .java)
91+
92+ override fun resolvePaths (architectures : Set <String >): FrameworkPaths {
93+ val xcodeprojPath = linker.xcodeprojPath.orNull
94+ val derivedDataPath = xcodeprojPath?.let { path ->
95+ project.providers.of(DerivedDataPathValueSource ::class .java) {
96+ it.parameters.xcodeprojPath.set(path)
97+ }.get()
98+ } ? : return FrameworkPaths .NONE
99+ val dynamicBasePath =
100+ " ${derivedDataPath} /SourcePackages/artifacts/sentry-cocoa/Sentry-Dynamic/Sentry-Dynamic.xcframework"
101+ val staticBasePath =
102+ " ${derivedDataPath} /SourcePackages/artifacts/sentry-cocoa/Sentry/Sentry.xcframework"
103+
104+ return FrameworkPaths .createValidated(
105+ dynamicBasePath = dynamicBasePath,
106+ staticBasePath = staticBasePath,
107+ architectures = architectures
108+ )
109+ }
110+ }
111+
112+ class ManualSearchStrategy (
113+ private val project : Project ,
114+ ) : FrameworkResolutionStrategy {
115+ override fun resolvePaths (architectures : Set <String >): FrameworkPaths {
116+ val dynamicValueSource =
117+ project.providers.of(ManualFrameworkPathSearchValueSource ::class .java) {
118+ it.parameters.frameworkType.set(FrameworkType .DYNAMIC )
119+ it.parameters.frameworkArchitectures.set(architectures)
120+ }
121+ val staticValueSource =
122+ project.providers.of(ManualFrameworkPathSearchValueSource ::class .java) {
123+ it.parameters.frameworkType.set(FrameworkType .STATIC )
124+ it.parameters.frameworkArchitectures.set(architectures)
125+ }
126+
127+ return FrameworkPaths .createValidated(
128+ dynamicBasePath = dynamicValueSource.orNull,
129+ staticBasePath = staticValueSource.orNull,
130+ architectures = architectures
131+ )
132+ }
133+ }
134+
135+ class FrameworkPathResolver (
136+ private val project : Project ,
137+ private val strategies : List <FrameworkResolutionStrategy > = defaultStrategies(project),
138+ ) {
139+ fun resolvePaths (
140+ architectures : Set <String >
141+ ): FrameworkPaths {
142+ return strategies.firstNotNullOfOrNull { strategy ->
143+ try {
144+ strategy.resolvePaths(architectures)
145+ } catch (e: Exception ) {
146+ project.logger.debug(
147+ " Path resolution strategy ${strategy::class .simpleName} failed" ,
148+ e
149+ )
150+ null
151+ }
152+ }
153+ ? : throw FrameworkLinkingException (" All framework resolution strategies failed. Could not find Sentry Cocoa framework path" )
154+ }
155+
156+ companion object {
157+ /* *
158+ * Resolution strategies for finding the framework path
159+ *
160+ * The order of resolution strategies matters, as the framework path will be resolved by the first successful strategy
161+ * Specifically here Custom Path will be checked first, if that fails then it is followed by the Derived Data strategy etc...
162+ */
163+ fun defaultStrategies (project : Project ): List <FrameworkResolutionStrategy > {
164+ return listOf (
165+ CustomPathStrategy (project),
166+ DerivedDataStrategy (project),
167+ ManualSearchStrategy (project)
168+ )
169+ }
170+ }
171+
172+ }
0 commit comments