Building with Poly/ML

This section describes how to build a stand-alone executable using Poly/ML.

Although Poly/ML provides the polyc utility to simplify creation of a stand-alone executable, this utility is not suitable for building applications based on Giraffe Library, neither for compilation nor for linking for the following reasons:

  1. During development, incremental compilation is desirable to avoid loading library dependencies for the entire GTK 3 stack on every compilation. polyc does not support incremental compilation so its use would substantially increase compilation times.

  2. polyc does not allow the caller to specify additional linker flags so it cannot be used for the linking stage.

Therefore, this section describes building without use of polyc.

Source files

We assume that the main program for the application is a function main : unit -> unit, as described in the section Main Program, and that this is defined in a source file app.sml.

For Poly/ML, the main program must be exported to an object file name.o. It is recommended that a separate source file does this, as follows, to keep app.sml portable:

polyml-export.sml

val () = PolyML.export (name, main)

These SML source files and their dependencies must be loaded in a Poly/ML session. The rest of this section describes SML declarations that achieve this.

To avoid hard-coding the location of the Giraffe Library installation in MLB files, the environment variable GIRAFFE_SML_LIB is used as a placeholder for the directory installdir/lib/sml where installdir is the Giraffe Library installation directory.

The dependencies provided by Giraffe Library require the file ${GIRAFFE_SML_LIB}/polyml.sml to be loaded first:

val () =
  case OS.Process.getEnv "GIRAFFE_SML_LIB" of
    SOME dir => PolyML.use (OS.Path.joinDirFile {dir = dir, file = "polyml.sml"})
  | NONE     => raise Fail "GIRAFFE_SML_LIB not set"

This defines a top-level function use that supports relative paths in recursive use and environment variable expansion:

  • if use file occurs in a file in a directory dir and file is a relative path, file is treated relative to dir;

  • in use file, occurrences of $(VAR) in file are expanded to the value of the environment variable VAR if defined and to the empty string if not defined.

This use function is more convenient that PolyML.use so is used in the subsequent SML declarations to load dependencies.

The library dependencies always include

val () = use "$(GIRAFFE_SML_LIB)/general/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/ffi/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gir/polyml.sml"

and include

val () = use "$(GIRAFFE_SML_LIB)/namespace-version/polyml.sml"

for each namespace Namespace-Version whose structure Namespace is referenced in the SML source or in another library dependency.

For example, if the SML source references the structure Gio to use the Gio-2.0 namespace, then the dependencies also include GLib and GObject which are referenced from Gio:

val () = use "$(GIRAFFE_SML_LIB)/glib-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gobject-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gio-2.0/polyml.sml"

The directory name namespace-version is Namespace-Version in lowercase but this is not necessarily the same as the pkg-config name, which is provided by the contents of the file namespace-version/package.

For example, for the namespace Gtk-3.0, the directory name is gtk-3.0 but the pkg-config name is gtk+-3.0.

Therefore, an SML file that loads the library dependencies and application source could have the following form:

polyml.sml

val () = use "$(GIRAFFE_SML_LIB)/general/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/ffi/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gir/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/namespace1-version1/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/namespace2-version2/polyml.sml"
…
val () = use "app.sml"

An overall script to load all files and export an object file could have the following form:

load-polyml.sml

val () =
  case OS.Process.getEnv "GIRAFFE_SML_LIB" of
    SOME dir => use (OS.Path.joinDirFile {dir = dir, file = "polyml.sml"})
  | NONE     => raise Fail "GIRAFFE_SML_LIB not set"
val () = use "polyml.sml"
val () = use "polyml-export.sml"

In practice, a build system would save the state of the session after loading the library dependencies so that these do not have to be recompiled each time the application is compiled. (This is done by the Make files supplied with the Giraffe Library Examples.)

Build commands

Separate commands are required for compilation and for linking:

  • poly is used to compile SML code and generate an object file.

  • gcc is used to link the object file with its library dependencies to create a stand-alone executable.

At a minimum, the poly command must do the following to compile the SML source files described in the previous section:

  • Set the environment variable GIRAFFE_SML_LIB.

  • Specify the SML file load-polyml.sml.

This is achieved by the following command:

GIRAFFE_SML_LIB=installdir/lib/sml \
 poly --script load-polyml.sml

where installdir is the Giraffe Library installation directory.

The poly command could usefully set the environment variable GIRAFFE_DEBUG to a non-empty value for diagnostic output. This is achieved by the following command:

GIRAFFE_SML_LIB=installdir/lib/sml \
 GIRAFFE_DEBUG=1 \
 poly --script load-polyml.sml

At a minimum, the gcc command must do the following to link the object file and generate an exectuable:

  • (Darwin only) If Poly/ML < 5.9 or Poly/ML generates X86/32 code, specify that a position-independent executable is not generated.

  • Set the run path for dynamically linked/loaded shared object files provided by Poly/ML and Giraffe Library.

  • Specify the executable file name to generate.

  • Specify the object file to link.

  • Specify the libraries required for linking and their paths.

This is achieved by the following command:

  • Under Linux:

    gcc \
     -Wl,-rpath,polymldir/lib \
     -Wl,-rpath,installdir/lib/polyml \
     -o name \
     name.o \
     -Linstalldir/lib/polyml \
     -lgiraffe-namespace1-version1 \
     -lgiraffe-namespace2-version2 \
     $(PKG_CONFIG_PATH=polymldir/lib/pkgconfig:${PKG_CONFIG_PATH} pkg-config --libs polyml \
                    $(cat installdir/lib/sml/namespace1-version1/package) \
                    $(cat installdir/lib/sml/namespace2-version2/package) \
                    …) \
     …
    
  • Under Darwin, the same as for Linux except that the ld flag -no_pie is also specified in case Poly/ML < 5.9 or Poly/ML generates X86/32 code:

    gcc \
     -Wl,-no_pie \
     -Wl,-rpath,polymldir/lib \
     -Wl,-rpath,installdir/lib/polyml \
     -o name \
     name.o \
     -Linstalldir/lib/polyml \
     -lgiraffe-namespace1-version1 \
     -lgiraffe-namespace2-version2 \
     $(PKG_CONFIG_PATH=polymldir/lib/pkgconfig:${PKG_CONFIG_PATH} pkg-config --libs polyml \
                    $(cat installdir/lib/sml/namespace1-version1/package) \
                    $(cat installdir/lib/sml/namespace2-version2/package) \
                    …) \
     …
    

For the library dependencies, it is not actually necessary to specify all of the namespaces that the SML code loads into a Poly/ML session, as shown in polyml.sml above. In particular, there is no need to mention namespace-version if the structure Namespace is referenced in a library dependency but not in the application's SML source because the pkg-config utility determines dependencies.

The gcc command could usefully include debugging information, e.g. using the -ggdb option.

For example, to build the Hello World example, the following files could be used:

polyml-export.sml

val () = PolyML.export ("example-1-polyml.o", main)

polyml.sml

val () = use "$(GIRAFFE_SML_LIB)/general/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/ffi/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gir/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/glib-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gobject-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gio-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gmodule-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/cairo-1.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/pango-1.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/pangocairo-1.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gdkpixbuf-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/atk-1.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gdk-3.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/xlib-2.0/polyml.sml"
val () = use "$(GIRAFFE_SML_LIB)/gtk-3.0/polyml.sml"
val () = use "example-1.sml"

load-polyml.sml

val () =
  case OS.Process.getEnv "GIRAFFE_SML_LIB" of
    SOME dir => use (OS.Path.joinDirFile {dir = dir, file = "polyml.sml"})
  | NONE     => raise Fail "GIRAFFE_SML_LIB not set"
val () = use "polyml.sml"
val () = use "polyml-export.sml"

and, given environment variables for the installation directories as follows:

GIRAFFEHOME=installdir
POLYMLHOME=polymldir

the following commands could be used.

  1. To compile:

    GIRAFFE_SML_LIB=${GIRAFFEHOME}/lib/sml GIRAFFE_DEBUG=1 poly --script load-polyml.sml
  2. To link:

    • Under Linux:

      gcc \
       -ggdb \
       -Wl,-rpath,${POLYMLHOME}/lib \
       -Wl,-rpath,${GIRAFFEHOME}/lib/polyml \
       -o example-1-polyml \
       example-1-polyml.o \
       -L${GIRAFFEHOME}/lib/polyml \
       -lgiraffe-gobject-2.0 \
       -lgiraffe-gio-2.0 \
       -lgiraffe-gtk-3.0 \
       $(PKG_CONFIG_PATH=${POLYMLHOME}/lib/pkgconfig:${PKG_CONFIG_PATH} pkg-config --libs polyml \
                      $(cat ${GIRAFFEHOME}/lib/sml/gobject-2.0/package) \
                      $(cat ${GIRAFFEHOME}/lib/sml/gio-2.0/package) \
                      $(cat ${GIRAFFEHOME}/lib/sml/gtk-3.0/package))
    • Under Darwin, the same as for Linux except that the ld flag -no_pie is also specified in case Poly/ML < 5.9 or Poly/ML generates X86/32 code:

      gcc \
       -ggdb \
       -Wl,-no_pie \
       -Wl,-rpath,${POLYMLHOME}/lib \
       -Wl,-rpath,${GIRAFFEHOME}/lib/polyml \
       -o example-1-polyml \
       example-1-polyml.o \
       -L${GIRAFFEHOME}/lib/polyml \
       -lgiraffe-gobject-2.0 \
       -lgiraffe-gio-2.0 \
       -lgiraffe-gtk-3.0 \
       $(PKG_CONFIG_PATH=${POLYMLHOME}/lib/pkgconfig:${PKG_CONFIG_PATH} pkg-config --libs polyml \
                      $(cat ${GIRAFFEHOME}/lib/sml/gobject-2.0/package) \
                      $(cat ${GIRAFFEHOME}/lib/sml/gio-2.0/package) \
                      $(cat ${GIRAFFEHOME}/lib/sml/gtk-3.0/package))