packages feed

xcframework-0.1.0.0: README.md

# 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!