xcframework: Cabal hooks for producing an XCFramework from a Haskell library

[ bsd3, distribution, library ] [ Propose Tags ] [ Report a vulnerability ]

Cabal hooks for producing an XCFramework from a Haskell library bundling the library binary artifact, the RTS and foreign-exports headers, and a modulemap exporting the headers as Swift modules.


[Skip to Readme]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.18 && <5), Cabal (>=3.14 && <4), Cabal-hooks (>=3.14 && <4), directory (>=1.3.8 && <2), filepath (>=1.5.2 && <2), process (>=1.6.19 && <2), temporary (>=1.3 && <1.4) [details]
License BSD-3-Clause
Copyright Copyright (C) 2025 Rodrigo Mesquita
Author Rodrigo Mesquita
Maintainer rodrigo.m.mesquita@gmail.com
Category Distribution
Home page https://github.com/alt-romes/haskell-swift
Bug tracker https://github.com/alt-romes/haskell-swift
Source repo head: git clone https://github.com/alt-romes/haskell-swift
Uploaded by romes at 2025-07-07T08:13:55Z
Distributions
Downloads 5 total (5 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for xcframework-0.1.0.0

[back to package description]

xcframework

Cabal hooks for producing an XCFramework 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.

How to Use

In your cabal file, change the build-type to Hooks (and set cabal-version: 3.14 if not set already):

- build-type:     Simple
+ build-type:     Hooks

And add a setup-depends stanza with a dependency on xcframework:

+ 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:

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:

  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

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-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:

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 exports 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 with a working setup -- if you want to try it out. Remember to build the Cabal library first!