Add XCFramework build script with C-ABI wrapper#3
Conversation
CInterop.swift exposes a C-ABI surface (@_cdecl) so the kit can be loaded from C/C++ apps as well as Swift apps. The wrapper bridges the Swift actor's async generate() through a semaphore so the C surface is blocking. build-xcframework.sh archives the SPM package per Apple-platform slice (macOS, iOS device/sim, visionOS device/sim), merges every transitive .a (mlx-swift, sentencepiece, etc.) with libtool, compiles mlx.metallib for the slice, assembles a static .framework bundle, and combines the slices with xcodebuild -create-xcframework. It temporarily rewrites the ../mlx-swift path dependency to a public git URL during the build. compile-mlx-metallib.sh now honors METAL_TARGET and MLX_SWIFT_DIR env vars so it can be called per-slice from the XCFramework build. https://claude.ai/code/session_01LgAV8cNyoB87u2zUVoTkpz
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1e360ab8f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # Copy every per-arch artifact (swiftmodule/swiftdoc/swiftinterface/abi.json). | ||
| find "$best" -maxdepth 1 -type f \ | ||
| \( -name "*.swiftmodule" -o -name "*.swiftdoc" \ | ||
| -o -name "*.swiftinterface" -o -name "*.private.swiftinterface" \ | ||
| -o -name "*.abi.json" -o -name "*.swiftsourceinfo" \) \ | ||
| -exec cp {} "$fw/Modules/$MODULE_NAME.swiftmodule/" \; |
There was a problem hiding this comment.
Include dependency Swift modules in the XCFramework
For Swift consumers, copying only StableAudioKit.swiftmodule leaves the generated .swiftinterface with unresolved public dependency types: AudioWriter.write(_ audio: MLXArray, ...) is public, so the interface imports/references MLX, but this script only libtool-merges the dependency binaries and never packages the dependency .swiftmodule/.swiftinterface files. A client that adds only this supposedly self-contained XCFramework and runs import StableAudioKit will fail to compile because it cannot load the MLX module; either hide those dependency types from the public API or include the required Swift modules alongside the framework.
Useful? React with 👍 / 👎.
Summary
Scripts/build-xcframework.shproduces a self-containedStableAudioKit.xcframeworkwith all transitive dependencies (mlx-swift, swift-sentencepiece, swift-numerics)libtool-merged into a single static framework binary, plus a per-slicemlx.metallib. Slices: macOS, iOS device/sim, visionOS device/sim.Sources/StableAudioKit/CInterop.swiftexposes the pipeline as a C-ABI surface (@_cdecl) so the framework can be loaded from C/C++ apps in addition to Swift apps. The wrapper bridgesStableAudioPipeline.generate's async actor method through aDispatchSemaphoreso the C entry points are blocking.Scripts/xcframework-resources/holds the public C header (StableAudioKit.h), frameworkmodule.modulemap(withlink "c++"for sentencepiece), andInfo.plist.tmpl.Scripts/compile-mlx-metallib.shnow honorsMETAL_TARGETandMLX_SWIFT_DIRso it can be retargeted per slice during the XCFramework build.../mlx-swiftpath dependency inPackage.swifttohttps://github.com/olilarkin/mlx-swift(override viaMLXSWIFT_BRANCH) and restores the file on exit.Usage
./Scripts/build-xcframework.sh # -> build/xcframework/StableAudioKit.xcframeworkC consumers
#include <StableAudioKit/StableAudioKit.h>; Swift consumersimport StableAudioKitand get the existing rich API. Weights and the SentencePiece tokenizer model are still user-supplied at runtime (the XCFramework ships code +mlx.metallibonly).Test plan
./Scripts/build-xcframework.shon macOS with Xcode 15+ and confirmbuild/xcframework/StableAudioKit.xcframeworkcontains all 5 slicesModules/StableAudioKit.swiftmodule/has a.swiftinterface(library evolution output)mlx.metallibof non-zero sizeimport StableAudioKit, runStableAudioPipeline.load(from:)+generate(_:)stable_audio_pipeline_create→stable_audio_generate→stable_audio_write_wavPackage.swiftis restored unchanged after the script runs (including after Ctrl-C mid-build)https://claude.ai/code/session_01LgAV8cNyoB87u2zUVoTkpz
Generated by Claude Code