I need to build an OCaml cross-compiler. Sadly, it seems this is not supported out of the box and needs a little work, as described for an older version of the OCaml compiler.
My first question is: What is a nice way to generate the files config/m.h, config/s.h and config/Makefile?

- 1,133
- 11
- 18
2 Answers
I've been building OCaml cross compilers for a few years now. (See my profile for a link to my website.) What I do is build the compiler 1 1/2 times. The first time is for the host (with some settings for the target). The second half build is to build the runtime for the target.
My script for building a cross compiler from OS X to to ARM/iOS is named xarm-build
. If you have Subversion, you can get a copy from my public repository:
$ svn cat svn://svn.psellos.com/trunk/ocamlxarm/3.1/xarm-build
Disclaimer: right now, this script just builds the bytecode version of the compiler. I.e., the compiler itself is an OCaml bytecode executable. However it produces native code for the target.
If you try this and have any questions, let me know.
To answer your specific question, if your target system is Unix-like you could try running the configure
script on the target to generate config/s.h
, config/m.h
, and config/Makefile
, which as you mention are the critical files. If there's a simulator for your target, you can run configure
inside the simulator--this is what I do for iOS. Otherwise you have to figure out reasonable contents yourself. (Maybe run configure on a Unix-like system that's as similar as possible to your target.)

- 65,646
- 2
- 72
- 108
-
Thanks for your answer. I generated the files on the DiskStation with the gcc provided by ipkg, which I assume is as close to the target system as one can get. I also read your script, and it troubles me a bit. First a trivial observation: You can save 4 lines by typing `-prefix $XARMTARGET/v7` in two configure calls. Further, I am a bit overwhelmed. What is this `ocamlopt` built in `build1`? Compiles ocaml to ARM, but without the right toolchain environment? And this is fixed with `build2`? And do you use implicitly that the ARM environment does not differ much from the i386 one? – Percival Ulysses Oct 06 '12 at 21:19
-
You need to build a compiler that generates code for your target. Being an OCaml program, this compiler needs a runtime that works on the host (notably, a bytecode interpreter). But you also need a runtime for the programs that get compiled by the compiler. This runtime works on the target. Since you need two runtimes, you have to build twice. (In older revs of OCaml, `-prefix` did not work for me. If it works now, great.) – Jeffrey Scofield Oct 06 '12 at 21:33
-
I modified the configure script so that the files can be generated with two runs (and posted it, would you give it a short look, please?). I have a few questions: I noticed that in the second phase of your script, you don't post-modify the `RANLIB*`, `ARCMD` and `MKLIB` lines in the Makefile, so these are only used on the host machine (the first phase)? For the modification of `utils/config.ml`, only the correct cross compiler must be provided? In the second phase, if the correct environment is provided, one must not modify anything? (Maybe this time I should ask a new SO question) – Percival Ulysses Oct 13 '12 at 22:55
-
My original script does two runs, that's exactly the point. So I'm not sure what your modifications are doing specifically. If they work for you, excellent! (With the original script you can say `xarm-build phase1` to run the first phase and `xarm-build phase2` for the second phase.) – Jeffrey Scofield Oct 13 '12 at 23:28
With a modified configure "chain" it is possible to generate the files.
Ocamls configure script assumes that it can compile and execute the results on the same run, which can be impossible in a cross compile environment.
Hence the configure procedure must be modified such that the results of the compilations (including the executables) are stored and can be used in a second run on the target machine. Here is the diff file showing the modification (~200 lines).
diff -r -U 1 ocaml-4.00.0-orig/config/auto-aux/hasgot ocaml-4.00.0-cross/config/auto-aux/hasgot
--- ocaml-4.00.0-orig/config/auto-aux/hasgot
+++ ocaml-4.00.0-cross/config/auto-aux/hasgot
@@ -15,2 +15,4 @@
+. ./keyval.sh
+
opts=""
@@ -36,7 +38,13 @@
+key="$cc $args"
+getValueExit "$key"
+
if test "$verbose" = yes; then
echo "hasgot $args: $cc $opts -o tst hasgot.c $libs" >&2
- exec $cc $opts -o tst hasgot.c $libs > /dev/null
+ `exec $cc $opts -o tst hasgot.c $libs > /dev/null`
else
- exec $cc $opts -o tst hasgot.c $libs > /dev/null 2>/dev/null
+ `exec $cc $opts -o tst hasgot.c $libs > /dev/null 2>/dev/null`
fi
+res=$?
+setValue "$key" "$res"
+exit "$res"
diff -r -U 1 ocaml-4.00.0-orig/config/auto-aux/hasgot2 ocaml-4.00.0-cross/config/auto-aux/hasgot2
--- ocaml-4.00.0-orig/config/auto-aux/hasgot2
+++ ocaml-4.00.0-cross/config/auto-aux/hasgot2
@@ -15,2 +15,4 @@
+. ./keyval.sh
+
opts=""
@@ -36,7 +38,13 @@
+key="$cc $args"
+getValueExit "$key"
+
if test "$verbose" = yes; then
echo "hasgot2 $args: $cc $opts -o tst hasgot.c $libs" >&2
- exec $cc $opts -o tst hasgot.c $libs > /dev/null
+ `exec $cc $opts -o tst hasgot.c $libs > /dev/null`
else
- exec $cc $opts -o tst hasgot.c $libs > /dev/null 2>/dev/null
+ `exec $cc $opts -o tst hasgot.c $libs > /dev/null 2>/dev/null`
fi
+res=$?
+setValue "$key" "$res"
+exit "$res"
Only in ocaml-4.00.0-cross/config/auto-aux: keyval.sh
diff -r -U 1 ocaml-4.00.0-orig/config/auto-aux/runtest ocaml-4.00.0-cross/config/auto-aux/runtest
--- ocaml-4.00.0-orig/config/auto-aux/runtest
+++ ocaml-4.00.0-cross/config/auto-aux/runtest
@@ -17,6 +17,30 @@
echo "runtest: $cc -o tst $* $cclibs" >&2
-$cc -o tst $* $cclibs || exit 100
+stream=/dev/stderr
else
-$cc -o tst $* $cclibs 2> /dev/null || exit 100
+stream=/dev/null
+#$cc -o tst $* $cclibs 2> /dev/null || exit 100
fi
+
+key="$* $cclibs"
+
+if test "$crossmode" = cross-cc; then
+ i=`cat ./counter`
+ $cc -o tst"$i" $* $cclibs 2> "$stream" || exit 100
+ echo "$key"'%%#%%'tst"$i" >> ./map_runtest
+ i=`expr $i + 1`
+ echo "$i" > ./counter
+ if test "$*" = sizes.c; then
+ echo "4 4 4 2"
+ fi
+ if test `expr "$*" : '.*tclversion.c'` -ne 0; then
+ echo "8.5"
+ fi
+ exit 0
+fi
+if test "$crossmode" = cross-run; then
+ tst=`awk -v ccargs="$key" 'BEGIN {FS="%%#%%"} $1 == ccargs {print $2}' ./map_runtest`
+ exec ./"$tst"
+fi
+
+$cc -o tst $* $cclibs 2> "$stream" || exit 100
exec ./tst
diff -r -U 1 ocaml-4.00.0-orig/config/auto-aux/tryassemble ocaml-4.00.0-cross/config/auto-aux/tryassemble
--- ocaml-4.00.0-orig/config/auto-aux/tryassemble
+++ ocaml-4.00.0-cross/config/auto-aux/tryassemble
@@ -1,8 +1,16 @@
#!/bin/sh
+
+. ./keyval.sh
+
+key="$aspp $*"
+getValueExit "$key"
+
if test "$verbose" = yes; then
echo "tryassemble: $aspp -o tst $*" >&2
-$aspp -o tst $* || exit 100
+`$aspp -o tst $* || exit 100`
else
-$aspp -o tst $* 2> /dev/null || exit 100
+`$aspp -o tst $* 2> /dev/null || exit 100`
fi
+res=$?
+setValue "$key" "$res"
@@ -11,7 +19,14 @@
if test "$verbose" = yes; then
+key="$as $*"
+getValueExit "$key"
echo "tryassemble: $as -o tst $*" >&2
-$as -o tst $* || exit 100
+`$as -o tst $* || exit 100`
else
-$as -o tst $* 2> /dev/null || exit 100
+`$as -o tst $* 2> /dev/null || exit 100`
fi
+res=$?
+setValue "$key" "$res"
+exit $res
+else
+exit $res
fi
diff -r -U 1 ocaml-4.00.0-orig/config/auto-aux/trycompile ocaml-4.00.0-cross/config/auto-aux/trycompile
--- ocaml-4.00.0-orig/config/auto-aux/trycompile
+++ ocaml-4.00.0-cross/config/auto-aux/trycompile
@@ -15,7 +15,15 @@
+. ./keyval.sh
+
+key="$cc $* $cclibs"
+getValueExit "$key"
+
if test "$verbose" = yes; then
echo "trycompile: $cc -o tst $* $cclibs" >&2
-$cc -o tst $* $cclibs || exit 100
+`$cc -o tst $* $cclibs || exit 100`
else
-$cc -o tst $* $cclibs 2> /dev/null || exit 100
+`$cc -o tst $* $cclibs 2> /dev/null || exit 100`
fi
+res=$?
+setValue "$key" "$res"
+exit $res
diff -r -U 1 ocaml-4.00.0-orig/configure ocaml-4.00.0-cross/configure
--- ocaml-4.00.0-orig/configure
+++ ocaml-4.00.0-cross/configure
@@ -47,2 +47,3 @@
withcamlp4=camlp4
+crossmode=''
@@ -119,2 +120,4 @@
withcamlp4="";;
+ -cross|--cross)
+ crossmode="$2"; shift;;
*) echo "Unknown option \"$1\"." 1>&2; exit 2;;
@@ -158,2 +161,21 @@
+case "$crossmode" in
+ cc)
+ crossmode=cross-cc
+ echo 0 > ./counter
+ rm -f ./map_runtest ./map_hasgot
+ touch ./map_runtest ./map_hasgot;;
+ run)
+ crossmode=cross-run
+ if test ! -e ./map_runtest -o ! -e ./map_hasgot; then
+ echo 'Run with -cross cc first'
+ exit 2
+ fi
+ rm -f ./counter;;
+ none) crossmode=none;;
+ "") crossmode=none ;;
+ *)
+ echo 'Unknown crossmode'>&2
+ exit 2;;
+esac
# Write options to Makefile
@@ -350,3 +372,3 @@
cc="$bytecc -O $bytecclinkopts"
-export cc cclibs verbose
+export cc cclibs verbose crossmode
@@ -1647,2 +1669,5 @@
+if test "$crossmode" = cross-run; then
+ rm -f tst* ./map_runtest ./map_hasgot
+fi
# Print a summary
The configure script gets a new -cross
option. When cc
is its argument, it only compiles, when it is run
, it only executes the compiled stuff. Intermediate results are stored in config/auto-aux/map_{hasgot,runtest}, mostly using setValue
and getValueExit
for retrieval, both defined in config/auto-aux/keyval.sh. If one supplies the cross toolchain data with
-cc
, -as
, -aspp
, -partialld
, -libs
, -dllibs
, -dldefs
the Makefile should be usable. Finally the file keyval.sh
whose content is not in the diff:
getValueExit()
{
if test "$crossmode" = cross-run; then
res=`awk -v ccargs="$1" 'BEGIN {FS="%%#%%"} $1 == ccargs {print $2; exit}' ./map_hasgot`
exit "$res"
fi
}
setValue()
{
if test "$crossmode" = cross-cc; then
echo "$1"'%%#%%'"$2" >> ./map_hasgot
fi
}
If tk
is used, one must modify config/auto-aux/runtest and replace 0.0
with its version number. Further, it may be necessary to modify the file config/auto-aux/solaris-ld if solaris is used as target or host machine.

- 1,133
- 11
- 18