# xcframework
Cabal hooks for producing an
[XCFramework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle)
from a Haskell library bundling the library binary artifact, the RTS and
foreign-exports headers, and a modulemap exporting the headers as Swift
modules.
Refer also to the [release blogpost](https://alt-romes.github.io/posts/2025-07-05-packaging-a-haskell-library-as-a-swift-binary-xcframework.html).
## How to Use
In your cabal file, change the `build-type` to `Hooks` (and set `cabal-version:
3.14` if not set already):
```diff
- build-type: Simple
+ build-type: Hooks
```
And add a `setup-depends` stanza with a dependency on `xcframework`:
```diff
+ custom-setup
+ setup-depends:
+ base >= 4.18 && < 5,
+ xcframework >= 0.1
```
Finally, create a file called `SetupHooks.hs` in the root of your Cabal package
with the following contents, substituting the `_build/MyHaskellLib.xcframework` string for the
filepath to where the `.xcframework` should be written:
```haskell
module SetupHooks ( setupHooks ) where
import Distribution.XCFramework.SetupHooks
setupHooks :: SetupHooks
setupHooks = xcframeworkHooks "_build/MyHaskellLib.xcframework"
```
Now, whenever you run `cabal build`, the libraries will also be bundled into an `.xcframework`.
## How to use the XCFramework in XCode
In XCode:
1. Navigate to the target settings of your project.
2. Find under "General" the "Frameworks, Libraries, and Embedded Content" (or similar) section.
3. Click the add button and add the `.xcframework` framework outputted at the specified path by Cabal
Now, in the entry Swift module, import the RTS and init/exit the RTS. For
instance, in a sample SwiftUI app:
```diff
import SwiftUI
+ import Haskell.Foreign.Rts
@main
struct MyExample: App {
+
+ init() {
+ hs_init(nil, nil)
+
+ NotificationCenter.default
+ .addObserver(forName: NSApplication.willTerminateNotification,
+ object: nil, queue: .main) { _ in
+ hs_exit()
+ }
+ }
+
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
Finally, in any Swift module, do `import Haskell.Foreign.Exports`. For now, the
name `Haskell.Foreign.Exports` is fixed and exports all foreign-exported
functions, but it could be improved in the future (perhaps it's a good task to
contribute a patch for!)
For example, if your Haskell module looked like:
```
module MyLib (doSomething) where
fib :: Integral b => Int -> b
fib n = round $ phi ** fromIntegral n / sq5
where
sq5 = sqrt 5 :: Double
phi = (1 + sq5) / 2
doSomething :: IO Int
doSomething = do
putStrLn "doing some thing"
return $ fib 42
foreign export ccall doSomething :: IO Int
```
In your Swift module you can now
```swift
import Haskell.Foreign.Exports
let x = doSomething()
```
## Building simple Swift package
The `.xcframework` can also be easily used in a standalone swift package built
with `swift build`.
In your `Package.swift`, add `MyHaskellLib.xcframework` as a binary target and
make it a dependency of your main target. For instance, a simple library would
look like:
```swift
// swift-tools-version: 6.1
import PackageDescription
let package = Package(
name: "MySwiftLib",
platforms: [
.macOS(.v15)
],
products: [
.library(name: "MySwiftLib", targets: ["MySwiftLib"])
],
targets: [
.target(name: "MySwiftLib", dependencies: ["MyHaskellLib"], path: "Swift"),
.binaryTarget(
name: "MyHaskellLib",
path: "haskell/_build/MyHaskellLib.xcframework"
)
]
)
```
Now you can use the `Haskell.Foreign.Export` import in any module in the
package as explained above, for instance in `Swift/MySwiftLib.hs`:
```swift
import Foundation
import Haskell.Foreign.Exports
public struct Fib {
var val: Int64
}
public func mkFib() -> Fib {
let x = doSomething()
return Fib(val: x)
}
```
Build the Swift package using `swift build` in the project root.
## Must use Cabal Foreign Library stanza
Unfortunately, while I don't figure out how to link the right amount of things
into the `.xcframework` after building a normal `library` component in Cabal,
the `foreign export`s must be exported from a `foreign-library` Cabal stanza:
```
foreign-library myexample
type: native-shared
options: standalone
other-modules: MyLib
build-depends: base ^>=4.20.0.0
hs-source-dirs: src
default-language: GHC2021
```
To clarify the instructions, I put together [a small demo
project](https://github.com/alt-romes/hs-xcframework-simple-demo/) with a
working setup -- if you want to try it out. Remember to build the Cabal library first!