Versions and tools used:

Xcode Version 9.2 (9C40b)

Cabal HEAD (commit 94a7374{target="_blank" rel="noopener noreferrer"})

Stack Version 1.6.3

LLVM Version 5.0.1

A lot of progress has been going on to make Haskell work on mobile natively, instead of e.g. generating JavaScript via GHCJS and using that. Unfortunately, not much documentation exists yet on how to build a project using these tools all together.

This post will be an attempt to piece together the tools and various attempts into a coherent step-by-step guide. We will start by setting up the tools needed, and then build an iOS app that runs in both the simulator and on the device itself (i.e. a x86 build and an arm build).

For the impatient and brave, simply,

clone down the MobileHaskellFun repository,

run ./setup-tools.sh to set up the tools,

to set up the tools, cd into Offie/hs-src/ build the package index ./call x86_64-apple-ios-cabal new-update --allow-newer , run ./call make iOS to compile the program for iOS,

into and finally launch Xcode and start the simulator.

A bunch of tools are needed, so we will set these up first. You might have some of these, but I will go through them anyways, for good measure. The steps will assume that we are on macOS for some parts, but it should not be part to adapt these to your system (all steps using brew ).

If you don’t have stack installed already, set it up with,

$ curl -sSL https://get.haskellstack.org/ | sh -sSL https://get.haskellstack.org/

We will collect all our tools and GHC versions in a folder in $HOME —for convenience—so first we are a going to create that directory,

$ mkdir -p ~/.mobile-haskell -p ~/.mobile-haskell

Next step is cloning down cabal and building cabal-install. This is necessary until new-update lands.

If you have cabal-install and a system GHC already, then you can try and install it via cabal new-build cabal-install instead, which is less brittle. I wanted to remove the need to setup these though, so I went with the ./bootstrap.sh approach.

NOTE: If you are having trouble with e.g. errors on packages being shadowed, try the good ol’ cabal-hell fix, and nuke ~/.ghc and ~/.cabal/ .

Install LLVM version 5,

$ brew install llvm install llvm

This should set up LLVM in /usr/local/opt/[email protected]/bin (or just /usr/local/opt/llvm/bin ), remember this path for later.

We’ll now set up the tools from http://hackage.mobilehaskell.org, namely the toolchain-wrapper and the different GHC versions we will use.

Let’s start off with getting our GHCs, by downloading ghc-8.4.0.20180109-x86_64-apple-ios.tar.xz and ghc-8.4.0.20180109-aarch64-apple-ios.tar.xz , for the simulator and device respectively. You can download the by cliking their links on the website, or curl them down with (the links are probably outdated soon, so replace the links with the ones on the site),

$ cd ~/.mobile-haskell ~/.mobile-haskell $ curl -o ghc-aarch64-apple-ios.tar.xz http://releases.mobilehaskell.org/x86_64-apple-darwin/9824f6e473/ghc-8.4.0.20180109-aarch64-apple-ios.tar.xz -o ghc-aarch64-apple-ios.tar.xz http://releases.mobilehaskell.org/x86_64-apple-darwin/9824f6e473/ghc-8.4.0.20180109-aarch64-apple-ios.tar.xz $ curl -o ghc-x86_64-apple-ios.tar.xz http://releases.mobilehaskell.org/x86_64-apple-darwin/9824f6e473/ghc-8.4.0.20180109-x86_64-apple-ios.tar.xz -o ghc-x86_64-apple-ios.tar.xz http://releases.mobilehaskell.org/x86_64-apple-darwin/9824f6e473/ghc-8.4.0.20180109-x86_64-apple-ios.tar.xz

Now, let’s unpack these into their own folders (assuming you’re still in ~/.mobile-haskell ),

$ mkdir -p ghc-aarch64-apple-ios && xz -d ghc-aarch64-apple-ios.tar.xz && tar -xf ghc-aarch64-apple-ios.tar -C ghc-aarch64-apple-ios -p ghc-aarch64-apple-ios-d ghc-aarch64-apple-ios.tar.xz-xf ghc-aarch64-apple-ios.tar -C ghc-aarch64-apple-ios $ mkdir -p ghc-x86_64-apple-ios && xz -d ghc-x86_64-apple-ios.tar.xz && tar -xf ghc-x86_64-apple-ios.tar -C ghc-x86_64-apple-ios -p ghc-x86_64-apple-ios-d ghc-x86_64-apple-ios.tar.xz-xf ghc-x86_64-apple-ios.tar -C ghc-x86_64-apple-ios

Next up is the toolchain-wrapper, which provides wrappers around cabal and other tools we need,

And that’s it! We have now set up all the tools we need for later. If you want all the steps as a single script, check out the setup script in the MobileHaskellFun repo.

Setting up the Xcode Project

Setting up Xcode is a bit of a visual process, so I’ll augment these steps with pictures, to hopefully make it clear what needs to be done.

First, let’s set up our Xcode project, by creating a new project.

Choose Single View Application ,

And set the name and location of your project,

Now, let’s add a folder to keep our Haskell code in and call it hs-src , by right-clicking our project and adding a New Group ,

Interlude: Set up the Haskell Code

Before we proceed, let’s set up the Haskell code. Navigate to the hs-src directory, and add the following files (don’t worry, we’ll go through their contents),

$ mkdir -p src -p src $ touch MobileFun.cabal cabal.project Makefile call LICENSE src/Lib.hs MobileFun.cabal cabal.project Makefile call LICENSE src/Lib.hs

./hs-src/cabal.project

We use the features of cabal.project to set our package repository to use the hackage.mobilehaskell.org overlay.

: . packages . mobilehaskell repository hackagemobilehaskell : http :// hackage . mobilehaskell . org / urlhttphackagemobilehaskellorg : True secure - keys : 8184c1f23ce05ab836e5ebac3c3a56eecb486df503cc28110e699e24792582da rootkeys8184c1f23ce05ab836e5ebac3c3a56eecb486df503cc28110e699e24792582da 81ff2b6c5707d9af651fdceded5702b9a6950117a1c39461f4e2c8fc07d2e36a 8468c561cd02cc7dfe27c56de0da1a5c1a2b1b264fff21f4784f02b8c5a63edd - threshold : 3 keythreshold

./hs-src/MobileFun.cabal

Just a simple cabal package setup.

: MobileFun name : 0.1 . 0.0 version : BSD3 license - file : LICENSE licensefile : Your Name author : email @ example . com maintaineremailexamplecom : Your Name copyright : Miscellaneous category - type : Simple build - version : >= 1.10 cabalversion library - source - dirs : src hssourcedirssrc - modules : Lib exposedmodules - depends : base >= 4.7 && < 5 builddependsbase - simple , freersimple - language : Haskell2010 defaultlanguage

./hs-src/Makefile

The Makefile simplifies a lot of the compilation process and passes the flags we need to use.

./hs-src/src/Lib.hs

Our Haskell code for now, is simply some C FFI that sets up a small toy function.

./hs-src/call

We use the call script to set up the various path variables that point to our tools, so we don’t need these polluting our global command space. If you’ve followed the setup so far, the paths should match out-of-the-box.

Compiling Our Haskell Code

First off, we need to build our package index, so run (inside hs-src ),

$ ./call x86_64-apple-ios-cabal new-update --allow-newer x86_64-apple-ios-cabal new-update --allow-newer Downloading the latest package lists from: the latest package lists from: - hackage.haskell.org hackage.haskell.org - hackage.mobilehaskell hackage.mobilehaskell

Now we can build our project by running make on our target. For now, we have only set up iOS, so this is what we will build.

$ ./call make iOS make iOS CABAL= x86_64-apple-ios-cabal make cabal-build x86_64-apple-ios-cabalcabal-build x86_64-apple-ios-cabal new-configure --disable-shared --enable-static --allow-newer --ghc-option=-fllvmng new-configure --disable-shared --enable-static --allow-newer --ghc-option=-fllvmng 'cabal.project.local' file already exists. Now overwriting it. already exists. Now overwriting it. Resolving dependencies... dependencies... Build profile: -w ghc-8.4.0.20180109 -O1 profile: -w ghc-8.4.0.20180109 -O1 In order, the following would be built (use -v for more details) : order, the following would be built (use -v for more details) - natural-transformation-0.4 (lib) ( requires download & build ) natural-transformation-0.4 (lib)download - transformers-compat-0.5.1.4 (lib) ( requires download & build ) transformers-compat-0.5.1.4 (lib)download - transformers-base-0.4.4 (requires download & build ) transformers-base-0.4.4 (requires download ... find . -path "*-ios*" -name "libHSMobileFun*ghc*.a" -exec lipo -create -output binaries/iOS/libHSMobileFun.a {} + . -path-name-exec lipo -create -output binaries/iOS/libHSMobileFun.a {}

We should now have our library file at hs-src/binaries/iOS/libHSMobileFun.a . If you change your project, to will probably need to run ./call make clean before running ./call make iOS again.

Back to Xcode

Now we need to tie together the Haskell code with Xcode. Drag-and-drop the newly created files into the hs-src group in Xcode (if it hasn’t found it by itself).

Since we are using Swift, we need a bridging header to bring our C prototypes into Swift. We’ll do this by adding an Objective-C file to the project, tmp.m , which will make Xcode ask if we want to create a bridging header, Offie-Bridging-Header.h , for which we will answer yes.

./Offie-Bridging-Header.h

In our bridging file, Offie-Bridging-Header.h , we add our prototypes that we need to glue in the Haskell code,

extern void hs_init( int * argc, char ** argv[]); hs_init(* argc,** argv[]); extern char * hello(); * hello();

./AppDelegate.swift

Now let’s go into AppDelegate.swift and call hs_init to initialize the Haskell code,

import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. hs_init(nil, nil) return true true } func applicationWillResignActive(_ application: UIApplication) {} func applicationDidEnterBackground(_ application: UIApplication) {} func applicationWillEnterForeground(_ application: UIApplication) {} func applicationDidBecomeActive(_ application: UIApplication) {} func applicationWillTerminate(_ application: UIApplication) {} }

./ViewController.swift

Next, we will set up a label in a view controller. You can either set this up in the story board and connect it via an IBOutlet .

First go into the Main.storyboard and create a label element somewhere on the screen.

Then enable the Assistant Editor in the top right cornor, and ctrl-click on the label, dragging it over to the ViewController.swift and name helloWorldLabel .

We can now set the text of the label by calling our Haskell function with cString: hello() , making our ViewController.swift look like,

import UIKit class ViewController: UIViewController { @IBOutlet var helloWorldLabel: UILabel! override func viewDidLoad() { super .viewDidLoad() .viewDidLoad() helloWorldLabel.text = String(cString: hello()) } override func didReceiveMemoryWarning() { super .didReceiveMemoryWarning() .didReceiveMemoryWarning() } }

Linking in our Haskell Library

The final step we need to do, is linking in our library that we built earlier, hs-src/binaries/iOS/libHSMobileFun.a , so that Xcode can find our C prototype functions.

We do this by going into Build Phases , which is exposed under the Xcode project settings, and click the + to add a new library,

Choose Add Other... to locate the library,

and finally locate the library file in hs-src/binaries/iOS/libHSMobileFun.a ,

We also need to set the build to not generate bytecode, because we are using the external GHC library. This is done under Build Settings , locating Enable Bitcode (e.g. via the search) and setting it to No .

Run the Code!

Final step, let’s run our code in the simulator

Congratulations! You’re now calling Haskell code from Swift and running it in an iOS simulator.

💡 You might run into a problem like could not create compact unwind for _ffi_call_unix64: does not use RBP or RSP based frame in your Xcode builds. You can fix this by adding libconv to your libraries in Build Phase .

Resources

Most of this is gathered from:

If you are interested in following the development of Haskell in the mobile space, I recommend following @zw3rktech and @mobilehaskell.

Finally, let me know if something is not working with the MobileHaskellFun repository. I haven’t dealt that much with setting up Xcode projects for sharing, so I’m a bit unclear on what settings follow the repository around.