This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at master 13 kB View raw
1import com.vanniktech.maven.publish.SonatypeHost 2import org.jetbrains.compose.ExperimentalComposeLibrary 3import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree 4 5mavenPublishing { 6 publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) 7 coordinates("com.performancecoachlab.posedetection", "posedetection-compose", "4.15.0") 8 9 pom { 10 name.set("Pose Detection") 11 description.set("real time body tracking for compose multiplatform mobile apps") 12 inceptionYear.set("2025") 13 url.set("https://tangled.sh/@nateholland.bsky.social/PoseDetection") 14 licenses { 15 license { 16 name.set("The Apache License, Version 2.0") 17 url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 18 distribution.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 19 } 20 } 21 developers { 22 developer { 23 id.set("nateholland") 24 name.set("Nate") 25 url.set("https://tangled.sh/@nateholland.bsky.social") 26 } 27 } 28 scm { 29 url.set("https://tangled.sh/@nateholland.bsky.social/PoseDetection") 30 connection.set("scm:git:git://tangled.sh/@nateholland.bsky.social/PoseDetection.git") 31 developerConnection.set("scm:git:ssh://git@tangled.sh:nateholland.bsky.social/PoseDetection") 32 } 33 } 34 // Only sign when explicitly enabled (CI / Maven Central). Local 35 // `publishToMavenLocal` runs for consumers like the kima app and 36 // doesn't need GPG signatures. 37 if (project.findProperty("signingEnabled") == "true") { 38 signAllPublications() 39 } 40} 41plugins { 42 alias(libs.plugins.multiplatform) 43 alias(libs.plugins.compose.compiler) 44 alias(libs.plugins.compose) 45 alias(libs.plugins.android.library) 46 id("com.vanniktech.maven.publish") version "0.31.0" 47} 48 49kotlin { 50 jvmToolchain(11) 51 androidTarget { 52 //https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-test.html 53 instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) 54 } 55 56 listOf( 57 iosX64(), 58 iosArm64(), 59 iosSimulatorArm64() 60 ).forEach { target -> 61 target.binaries.framework { 62 baseName = "ComposeApp" 63 isStatic = true 64 } 65 // MLKit pose detection is embedded via cinterop `staticLibraries` for 66 // iosArm64 only — the library's klib ships the MLKit binaries so 67 // downstream consumers get MLKit without running cocoapods themselves. 68 // Sim targets fall back to Apple Vision (upstream MLKit has no 69 // arm64-simulator slice). 70 if (target.name == "iosArm64") { 71 target.compilations.getByName("main").cinterops.create("mlkitAccurate") { 72 defFile(layout.buildDirectory.file("mlkit-archives/mlkitAccurate.def").get().asFile) 73 packageName("cocoapods.MLKitPoseDetectionAccurate") 74 } 75 } 76 } 77 78 sourceSets { 79 commonMain.dependencies { 80 implementation(compose.runtime) 81 implementation(compose.foundation) 82 implementation(compose.material3) 83 implementation(compose.components.resources) 84 implementation(compose.components.uiToolingPreview) 85 api("co.touchlab:kermit:2.0.4") 86 } 87 88 commonTest.dependencies { 89 implementation(kotlin("test")) 90 @OptIn(ExperimentalComposeLibrary::class) 91 implementation(compose.uiTest) 92 } 93 94 androidMain.dependencies { 95 implementation(compose.uiTooling) 96 implementation(libs.androidx.activityCompose) 97 implementation(libs.androidx.camera.core) 98 implementation(libs.androidx.camera.camera2) 99 implementation(libs.androidx.camera.lifecycle) 100 implementation(libs.androidx.camera.video) 101 implementation(libs.androidx.camera.view) 102 implementation(libs.androidx.camera.extensions) 103 implementation(libs.androidx.camera.compose) 104 implementation(libs.pose.detection) 105 implementation(libs.pose.detection.common) 106 implementation(libs.androidx.media3.common.ktx) 107 implementation(libs.litert) 108 implementation(libs.litert.support) 109 implementation(libs.litert.metadata) 110 implementation(libs.litert.gpu) 111 } 112 113 } 114} 115 116android { 117 namespace = "com.performancecoachlab.posedetection" 118 compileSdk = 36 119 defaultConfig { 120 minSdk = 21 121 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 122 } 123 buildFeatures { 124 mlModelBinding = true 125 } 126} 127 128//https://developer.android.com/develop/ui/compose/testing#setup 129dependencies { 130 androidTestImplementation(libs.androidx.uitest.junit4) 131 debugImplementation(libs.androidx.uitest.testManifest) 132} 133 134// ============================================================================ 135// MLKit iOS integration 136// ---------------------------------------------------------------------------- 137// The library's iosArm64 klib ships MLKit statically-embedded via cinterop 138// `staticLibraries`. Downstream consumers get MLKit symbols without needing 139// cocoapods — standard Maven/Gradle KMP dependency resolution is enough. 140// 141// The `syncMlkitBinaries` task runs tools/sync-mlkit.sh, which fetches MLKit 142// pods into build/mlkit-staging and extracts per-target static archives into 143// build/mlkit-archives. `generateMlkitDefFile` then writes a cinterop .def 144// referencing those archives with absolute paths resolved at configuration 145// time. `cinteropMlkitAccurateIosArm64` is wired to depend on both. 146// ============================================================================ 147 148val mlkitStagingDir = layout.buildDirectory.dir("mlkit-staging") 149val mlkitArchivesDir = layout.buildDirectory.dir("mlkit-archives") 150val mlkitDefFile = layout.buildDirectory.file("mlkit-archives/mlkitAccurate.def") 151val mlkitRedirectSrc = 152 project.file("src/nativeInterop/mlkitRedirect/MLKitResourceRedirect.m") 153val mlkitRedirectDir = layout.buildDirectory.dir("mlkit-redirect") 154val mlkitRedirectLib = 155 layout.buildDirectory.file("mlkit-redirect/ios_arm64/libMLKitRedirect.a") 156val mlkitComposeResDir = layout.buildDirectory.dir("mlkit-compose-resources") 157 158// Compile our NSBundle swizzle for iosArm64 and archive into libMLKitRedirect.a. 159// The cinterop .def includes this static library alongside MLKit's, so the 160// swizzle gets linked into the consumer's KMP framework automatically. 161val compileMlkitRedirect = tasks.register<Exec>("compileMlkitRedirect") { 162 group = "mlkit" 163 description = "Compile NSBundle MLKit redirect swizzle to static archive." 164 inputs.file(mlkitRedirectSrc) 165 outputs.file(mlkitRedirectLib) 166 val out = mlkitRedirectLib.get().asFile 167 val obj = out.resolveSibling("MLKitResourceRedirect.o") 168 out.parentFile.mkdirs() 169 commandLine( 170 "sh", "-c", 171 "xcrun --sdk iphoneos clang " + 172 "-c -fobjc-arc -fmodules " + 173 "-arch arm64 -mios-version-min=16.2 " + 174 "${mlkitRedirectSrc.absolutePath} -o ${obj.absolutePath} && " + 175 "xcrun ar rcs ${out.absolutePath} ${obj.absolutePath}" 176 ) 177} 178 179// Copy MLKit resource bundles (.tflite / .binarypb) from the synced pods into 180// the library's iosMain Compose Multiplatform resources, so they're packaged 181// into the consumer app bundle automatically. At runtime, Kotlin extracts them 182// to NSCachesDirectory and the NSBundle swizzle redirects MLKit's lookups. 183val stageMlkitResources = tasks.register<Copy>("stageMlkitResources") { 184 group = "mlkit" 185 description = "Stage MLKit resource bundles as Compose MP resources." 186 dependsOn("syncMlkitBinaries") 187 val pods = mlkitStagingDir.get().asFile.resolve("Pods") 188 from(pods.resolve("MLKitPoseDetectionAccurate/Resources/MLKitPoseDetectionAccurateResources")) { 189 into("files/mlkit/MLKitPoseDetectionAccurateResources") 190 } 191 from(pods.resolve("MLKitPoseDetectionCommon/Resources/MLKitPoseDetectionCommonResources")) { 192 into("files/mlkit/MLKitPoseDetectionCommonResources") 193 } 194 from(pods.resolve("MLKitXenoCommon/Frameworks/MLKitXenoCommon.framework/MLKitXenoResources.bundle")) { 195 into("files/mlkit/MLKitXenoResources") 196 } 197 into(mlkitComposeResDir) 198} 199 200// Wire the staged MLKit resources into the Compose Multiplatform resource set 201// for the ios source tree. Compose packages these into the consumer's app 202// bundle at `compose-resources/<pkg>.generated.resources/files/mlkit/...`. 203compose.resources { 204 customDirectory( 205 sourceSetName = "iosMain", 206 directoryProvider = mlkitComposeResDir, 207 ) 208} 209 210// Compose's `generateResourceAccessors*` tasks read from the resource dir, so 211// make them wait for the stage task (which populates the dir). 212tasks.matching { it.name.startsWith("generateResourceAccessors") } 213 .configureEach { dependsOn(stageMlkitResources) } 214tasks.matching { it.name.startsWith("generateExpectResourceCollectors") } 215 .configureEach { dependsOn(stageMlkitResources) } 216tasks.matching { it.name.startsWith("convertXmlValueResources") } 217 .configureEach { dependsOn(stageMlkitResources) } 218tasks.matching { it.name.startsWith("copyNonXmlValueResources") } 219 .configureEach { dependsOn(stageMlkitResources) } 220tasks.matching { it.name.startsWith("prepareComposeResources") } 221 .configureEach { dependsOn(stageMlkitResources) } 222 223val syncMlkitBinaries = tasks.register<Exec>("syncMlkitBinaries") { 224 group = "mlkit" 225 description = "Fetch MLKit pods + extract static archives for each Kotlin iOS target." 226 inputs.file("tools/sync-mlkit.sh") 227 outputs.dir(mlkitArchivesDir) 228 executable = project.file("tools/sync-mlkit.sh").absolutePath 229 environment("MLKIT_STAGING_DIR", mlkitStagingDir.get().asFile.absolutePath) 230 environment("MLKIT_ARCHIVES_DIR", mlkitArchivesDir.get().asFile.absolutePath) 231 // Only iosArm64 (iPhone device) is supported upstream; sim targets fall 232 // back to Vision via the MlKitPose expect/actual stub. 233 environment("MLKIT_TARGETS", "ios_arm64") 234} 235 236val generateMlkitDefFile = tasks.register("generateMlkitDefFile") { 237 group = "mlkit" 238 description = "Generate the MLKit cinterop .def with absolute archive paths." 239 dependsOn(syncMlkitBinaries, compileMlkitRedirect) 240 outputs.file(mlkitDefFile) 241 // Capture as locals so the closure doesn't hold project-level refs — 242 // configuration-cache-safe. 243 val pods = mlkitStagingDir.get().asFile.resolve("Pods").absolutePath 244 val archives = mlkitArchivesDir.get().asFile.absolutePath 245 val redirectArchives = mlkitRedirectDir.get().asFile.absolutePath 246 val defFile = mlkitDefFile.get().asFile 247 doLast { 248 val defContent = buildString { 249 appendLine("language = Objective-C") 250 appendLine("modules = MLKitPoseDetectionAccurate MLKitPoseDetectionCommon MLKitVision") 251 appendLine("package = cocoapods.MLKitPoseDetectionAccurate") 252 appendLine( 253 "compilerOpts = -fmodules " + 254 "-F$pods/MLKitPoseDetectionAccurate/Frameworks " + 255 "-F$pods/MLKitPoseDetectionCommon/Frameworks " + 256 "-F$pods/MLKitVision/Frameworks " + 257 "-F$pods/MLKitCommon/Frameworks " + 258 "-F$pods/MLImage/Frameworks " + 259 "-F$pods/MLKitXenoCommon/Frameworks" 260 ) 261 appendLine( 262 "staticLibraries = " + 263 "libMLKitPoseDetectionAccurate.a libMLKitPoseDetectionCommon.a " + 264 "libMLKitVision.a libMLKitCommon.a libMLImage.a libMLKitXenoCommon.a " + 265 "libGTMSessionFetcher.a libGoogleDataTransport.a libGoogleToolboxForMac.a " + 266 "libGoogleUtilities.a libFBLPromises.a libnanopb.a libMLKitRedirect.a" 267 ) 268 appendLine("libraryPaths.ios_arm64 = $archives/ios_arm64 $redirectArchives/ios_arm64") 269 appendLine( 270 "linkerOpts = -ObjC -lc++ -lsqlite3 -lz " + 271 "-framework Accelerate -framework CoreML" 272 ) 273 // Expose the swizzle control function to Kotlin. 274 appendLine("---") 275 appendLine("void mlkit_set_resource_dir(const char *path);") 276 } 277 defFile.writeText(defContent) 278 } 279} 280 281tasks.matching { it.name.startsWith("cinteropMlkitAccurate") }.configureEach { 282 dependsOn(generateMlkitDefFile) 283 // Cinterop's up-to-date check only watches its .def file — a content 284 // change in the static archives (e.g. libMLKitRedirect.a after an .m 285 // edit) won't invalidate the produced klib. Declare the archive dirs 286 // as explicit inputs so any rebuilt .a forces cinterop + downstream 287 // klib + publish to re-run. 288 inputs.dir(mlkitArchivesDir) 289 inputs.dir(mlkitRedirectDir) 290}