Psellos
Life So Short, the Craft So Long to Learn

Compile OCaml for iOS (iPhone, iPad)

December 15, 2014

Note: This is an archived version of the OCamlXARM page, for those interested in earlier versions. The most recent version is at Compile OCaml for iOS.

OCamlXARM is a version of OCaml that cross-compiles to iOS devices. It runs on OS X, and builds apps for the iPhone, iPad, and iPod touch. The current version of OCamlXARM is 4.0, based on OCaml 4.01.0.

If you’re not familiar with OCaml, it’s a functional language in the same family as Haskell, Scala, and (especially) Standard ML. It’s powerful, flexible, efficient, and rigorous. I’ve found programming in OCaml for iOS to be productive and even delightful.

You can download an OCamlXARM package for Mavericks (OS X 10.9) or later from this link:

The package installs OCamlXARM under /usr/local/ocamlxarm. It was built under Mavericks using Xcode 5.1.1. The compiler builds against the iOS 7.1 SDK by default, but you can build apps for a later iOS revision using a small script ocamloptrev that I give below.

If you want more control over the installation and the default SDK, you can build OCamlXARM from sources as described at the end of this page under Building from Sources.

To a large extent, OCamlXARM is a modification of the OCaml ARM code generator written by Benedikt Meurer. I modified the compiler to do cross compiling from the Mac to iOS, and modified Meurer’s code generator for the iOS toolchain and ABI.

The ARM architectures supported by OCamlXARM are dictated by those supported by the OCaml ARM code generator. OCamlXARM 4.0 builds apps for the arm7 architecture, which can run on all iOS devices introduced since September 2009. Support for arm64 should be available in the next major release (OCamlXARM 5.0), as support for 64-bit ARM is being added in OCaml 4.02.0.

The OCamlXARM compiler can be used to produce real-world iOS apps. At Psellos we used OCamlXARM to build our commercial iOS apps, Master Schnapsen/66 and Cassino, and it has been used to build iOS apps by other developers as well. For one example, see SEAiq, a suite of iPad apps for marine navigation (in current use on several of the seven seas).

 

Our OCaml Programming page has a short pitch of the strengths of OCaml, and more resources for doing OCaml programming for iOS. For example, I’ve released sources for five example apps (shown in miniature at left) that you can compile and run on an iOS device or in the iOS Simulator.

Previous versions of OCamlXARM, for earlier versions of OS X and Xcode, can be found in the OCaml Programming Archives.

Compile for iOS 8.1

To compile for iOS 8.1 (or any recent revision), you can use this script (ocamloptrev):

#!/bin/bash
#
# ocamloptrev     ocamlopt for specified iOS revision
#
USAGE='ocamloptrev  -rev M.N  other-ocamlopt-options ...'

OCAMLDIR=/usr/local/ocamlxarm/v7

REV=''
declare -a ARGS
while [ $# -gt 0 ] ; do
    case $1 in
    -rev)
        if [ $# -gt 1 ]; then
            REV=$2
            shift 2
        else
            echo "$USAGE" >&2
            exit 1
        fi
        ;;
    *)  ARGS[${#ARGS[*]}]="$1"
        shift 1
        ;;
    esac
done
if [ "$REV" = "" ]; then
    echo "$USAGE" >&2
    exit 1
fi

HIDEOUT=/Applications/Xcode.app/Contents/Developer 
PLT=$HIDEOUT/Platforms/iPhoneOS.platform 
SDK=/Developer/SDKs/iPhoneOS${REV}.sdk 
OCAMLC=$OCAMLDIR/bin/ocamlopt
$OCAMLC -ccopt -isysroot -ccopt "$PLT$SDK" "${ARGS[@]}"

To use the script, copy/paste from this page or download from this link:

ocamloptrev, compile OCaml for specified iOS revision

Supply the desired iOS revision as -rev M.N. The script uses the -ccopt flag of ocamlopt to set the name of the desired iOS SDK.

Test

After installing, you might want to compile a tiny test program using the ocamloptrev script. Here’s how to compile for iOS 8.1:

$ cat > hello.ml
let main () = Printf.printf "Hello, world!\n"

let () = main ()
^D
$ ocamloptrev -rev 8.1 -ccopt -Wl,-no_pie -o hello hello.ml
$ file hello
hello: Mach-O executable arm

If the compiler produces an ARM executable binary as shown here, it is almost certainly working correctly.

You might instead see the test fail as follows:

$ ocamloptrev -rev 7.1 -ccopt -Wl,-no_pie -o hello hello.ml
clang: warning: no such sysroot directory: '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk'
ld: library not found for -lSystem
clang: error: linker command failed with exit code 1 (use -v to see invocation)
File "caml_startup", line 1:
Error: Error during linking

This most likely indicates that you’re asking for a revision of iOS for which the SDK isn’t installed on your system.

Further Information

Installing the OCaml cross compiler is only the first step. The accompanying notes Portland: Which Way Is Up on iOS? and Slide24: Sliding Tile Puzzle for iOS show how to build simple iOS apps and run them on an iOS device.

I’ve also put together a compiler named OCamlXSim that can be used to compile OCaml apps and run them in the iOS Simulator. This is a simpler way to get started, as it doesn’t require an iOS device or registration with Apple. OCamlXSim is described in Compile OCaml for iOS Simulator. In fact the Simulator is invaluable while developing for actual iOS devices—it provides quicker turnarounds in a self-contained environment (with no need for an extra test device).

If you have any questions, comments, or corrections, feel free to leave them below, or email me at jeffsco@psellos.com.

Appendix: Building from Sources

For maximum flexibility, you can build OCamlXARM from sources. This is a little more complicated than a standard OCaml build, but I’ve created a shell script that does it for you.

It’s complicated because the standard OCaml release is not designed to run as a cross compiler. In essence, the OCaml build system doesn’t ordinarily need to distinguish between generating the compiler itself, which we want to run on the Mac, and generating the runtime, which needs to run on iOS.

The trick I’ve used in OCamlXARM is to build two distinct copies of the OCaml runtime. The first copy is targeted at OS X, and powers the cross compiler itself. The second copy is targeted at iOS, and powers the iOS apps. By lucky chance, there are no parts of the runtime that absolutely need to work in both environments. As a result, the two runtimes can coexist inside a single OCaml release. (This would not be the case if, for example, I wanted to support the bytecode interpreter in iOS apps. For now, I’m sticking with native code apps.)

A second trick, which isn’t strictly necessary, is that I use the iOS Simulator execution environment (though not the actual simulator itself) to create the initial configuration settings for building the iOS runtime. The Simulator embodies a reasonably faithful copy of the properties of iOS (available system calls, sizes of integers, endianness, and so on), so it works as an environment for setting up most of the configuration automatically.

Please note that a few of the command lines of the following discussion are too long to fit on a single line of a typical browser window. In a lot of cases there is no good place to split them into smaller lines, usually because of a long filename or URL. Take care that you enter them as a single line if you are typing them in by hand.

Preliminaries

To develop and run code on iOS devices, you need an installation of Apple’s Xcode programming tools and you need to register with Apple as an iOS developer. The Xcode tools are free, but there is a yearly fee to register as an iOS developer. If you want to get started without paying the fee, you can develop for the iOS Simulator using OCamlXSim, described in Compile OCaml for iOS Simulator. To register as an iOS developer, see Apple’s iOS Developer Program page.

You can download Xcode (for free) from the Mac App Store. See Apple’s Xcode page for more details. Recent versions of Xcode contain the traditional Unix command-line tools, which you will also need to build OCamlXARM. (In earlier versions of Xcode, the command-line tools were downloaded separately.)

To simplify the command lines below, I’ll define some shell variables as abbreviations for some of the key paths of development tools. If you’re following along at the keyboard, you should define them yourself for later use:

$ HIDEOUT=/Applications/Xcode.app/Contents/Developer
$ TOOLDIR=$HIDEOUT/Toolchains/Xcodedefault.xctoolchain/usr/bin
$ PLT=$HIDEOUT/Platforms/iPhoneOS.platform

The OCaml native code compiler (ocamlopt) uses an external assembler to produce its final object files. In the same way, OCamlXARM’s ocamlopt uses the cross-assembler of the iOS SDK to produce iOS object files. After you’ve installed Xcode, you should find an assembler for the iOS platform:

$ (cd $TOOLDIR; ls -l as)
-rwxr-xr-x  1 root  wheel  27808 Mar 11 04:37 as

This is the assembler OCamlXARM will use, so it must be present. If it isn’t, check that you’ve installed Xcode (version 5.1 or later). If you’ve installed it in a non-standard location (other than /Applications), you’ll need to modify the shell variables accordingly.

In recent years, Apple has switched from the GNU C compiler gcc to a new compiler clang, based on LLVM. If you want to build OCamlXARM from sources, you should have a native C compiler and a C cross compiler for iOS as follows:

$ (cd /usr/bin; ls -l clang)
-rwxr-xr-x  1 root  wheel  14224 Oct 30  2013 clang
$ (cd $TOOLDIR; ls -l clang)
-rwxr-xr-x  1 root  wheel  36874304 May  9 21:09 clang

If these compilers aren’t present, make sure you have installed Xcode as described above, and that your version of Xcode includes the command-line tools.

Create Sources from Patches

To create sources from patches, you need to download the OCaml 4.01.0 release from INRIA and the patches from Psellos and then apply the patches.

Download the sources for the OCaml 4.01.0 release from INRIA:

$ curl -O -s http://caml.inria.fr/pub/distrib/ocaml-4.01/ocaml-4.01.0.tar.gz
$ ls -l ocaml-4.01.0.tar.gz
-rw-r--r--  1 jeffsco  staff  4397871 Aug 24 10:18 ocaml-4.01.0.tar.gz

Download patches from Psellos:

$ curl -O -s http://psellos.com/pub/ocamlxarm/ocamlxarm-4.0.2.diff
$ ls -l ocamlxarm-4.0.2.diff
-rw-r--r--  1 jeffsco  staff  55018 Aug 24 10:19 ocamlxarm-4.0.2.diff

To save typing, you can download directly from your browser:

Unpack the OCaml sources and apply the patches.

$ tar -xf ocaml-4.01.0.tar.gz
$ cd ocaml-4.01.0
$ patch -p0 < ../ocamlxarm-4.0.2.diff
patching file configure
patching file VERSION
patching file asmcomp/arm/arch.ml
patching file asmcomp/arm/emit.mlp
patching file asmcomp/arm/proc.ml
patching file asmcomp/arm/selection.ml
patching file asmcomp/interf.ml
patching file tools/make-package-macosx
patching file Makefile
patching file xarm-build
patching file asmrun/signals_osdep.h
patching file asmrun/arm.S
patching file asmrun/Makefile
$

Check out Sources from Repository

You can also check out the sources for OCamlXARM 4.0.2 from Psellos’s public Subversion repository. This is simpler, but it doesn’t give you the opportunity to examine the patches separately before applying them.

Check out sources for OCamlXARM 4.0.2:

$ svn co svn://svn.psellos.com/tags/ocamlxarm-4.0.2 ocamlxarm-4.0
$ cd ocamlxarm-4.0

These sources are identical to what you get if you apply the 4.0.2 patches to the INRIA 4.01.0 release, as above.

Build OCamlXARM

Once you have the sources, you’re ready to build OCamlXARM. The file xarm-build is a shell script that does the building. You may want to modify the script before running it. At the beginning of the script are the following lines:

export HIDEOUT=/Applications/Xcode.app/Contents/Developer
export TOOLDIR=$HIDEOUT/Toolchains/XcodeDefault.xctoolchain/usr/bin
export PLT=$HIDEOUT/Platforms/iPhoneOS.platform
export SDK=/Developer/SDKs/iPhoneOS7.1.sdk
export SIMPLT=$HIDEOUT/Platforms/iPhoneSimulator.platform
export SIMSDK=/Developer/SDKs/iPhoneSimulator7.1.sdk
export XARMTARGET=/usr/local/ocamlxarm
export OSXARCH=i386

The values are as follows:

HIDEOUT Secret hideout of Apple developer files (inside the Xcode app).
TOOLDIR Location of developer tools.
PLT Location of iOS platform directory.
SDK Desired iOS SDK version (a subdirectory of PLT—normally, the most recent available).
SIMPLT Location of iOS Simulator platform directory.
SIMSDK Desired iOS Simulator SDK version (a subdirectory of SIMPLT—should be the same version number as SDK).
XARMTARGET Where OCamlXARM should be installed.
OSXARCH Architecture for OS X executables (i386 or x86_64).

The value of XARMTARGET is a directory where OCamlXARM should be installed. This location is compiled into the OCamlXARM tools; that is, they are aware of their own installation location. This is convenient, but it also means that the tools actually need to be installed in this one specific place. It’s not possible (or at least not convenient at all) to move them somewhere else later on.

OCamlXARM 4.0 will be installed in a subdirectory of XARMTARGET named v7. Future releases will create more subdirectories for other ARM architectures as necessary.

Ordinarily, xarm-build will create 32-bit (i386 architecture) OS X executables. This is simplest, because they work on all Intel Macs. If you want to create 64-bit executables, change the value of OSXARCH to x86_64. In my initial testing, I’ve found that the 64-bit executables make compiles go a little faster—but only around 10%.

To build OCamlXARM all in one step:

$ sh xarm-build all > xarm-build.log 2>&1

If things go well, xarm-build.log will contain around 2,840 lines of output, ending with something like this:

../../boot/ocamlrun ../../tools/ocamlmklib -ocamlc '../../ocamlcomp.sh -I ../unix' -o unix -linkall unix.cmo ../unix/unixLabels.cmo
make: Nothing to be done for `allopt'.

If there is a problem, it may be possible to figure out what went wrong by looking at the error messages in xarm-build.log. It’s also possible to build OCamlXARM in smaller stages—see the script for details.

Now install the cross compiler.

$ sudo make install
Password:
    . . .
  install /usr/local/ocamlxarm/v7/lib/ocaml/ocamlbuild/ocamlbuild.cmo
  install /usr/local/ocamlxarm/v7/man/man1/ocamlbuild.1
$

The installation process produces around 285 lines of output.

Now you’re ready to test the compiler as described above under Test.