Please note:The SCons wiki is now restored from the attack in March 2013. All old passwords have been invalidated. Please reset your password if you have an account. If you note missing pages, please report them to webmaster@scons.org. Also, new account creation is currently disabled due to an ongoing spam flood (2013/08/27).

O'Caml Builder

O'Caml is statically-typed mixed paradigm programming language. Its toolchain functions somewhat differently from that of gcc, and it's currently unsupported by SCons. Since I'd like to use SCons to build an O'Caml project, I'm adding the necessary code.

Please note that I'm a novice when it comes to SCons, so if you can improve something on this page, please do!

Russel Winder has created a Bazaar branch of a SCons OCaml tool based on this code. It can be found at https://code.launchpad.net/~russel/sconsaddons/ocaml

Tools

Let's have a closer look at the tools provided by the O'Caml distribution:

ocaml

a interactive bytecode interpreter

ocamlc

a bytecode compiler

ocamlopt

a native code compiler

ocamlfind

an environment querying tool

ocamldep

a utility for determining build dependencies

ocamldoc

a utility for generating documentation embedded in source code

ocamlmktop

Building custom toplevel systems

ocamlyacc

Ocaml Yacc

ocamllex

Ocaml Lex

ocamlmklib

generate libraries with mixed C / Caml code

Extensions

.ml

a source file

.mli

a module interface file

.cmi

a compiled interface

.cmo

a bytecode compiled object

.cma

a bytecode library

.cmx

a native compiled object

.cmxa

a native library

.mll

Ocaml Lex file

.mly

Ocaml Yacc file

Scanners

O'Caml modules are opened using the syntax:

open ModuleName

The above is O’Caml’s equivalent to Python’s from ModuleName import *. The scanner below won’t find dependencies on modules that aren’t open’ed, such as just using the module name. For instance, to use the map function from the List module, you just say List.map, and you don’t need to mention List anywhere else. You’ll need to use the ocamldep command to get the dependencies, in general.

Here's a scanner:

   1 # use re.MULTILINE flag, so '^' matches at the beginning of the
   2 # string and at the beginning of each line, i.e. after each newline.
   3 # Matching of '$' is similary affected.
   4 open_re = re.compile('^open\\s+(\\S+)$', re.MULTILINE)
   5 def scan_ml_for_deps(node):
   6   contents = node.get_contents()
   7   found = open_re.findall(contents)
   8   return map(str.lower, found)
   9 # Bytecode
  10 def mlfile_scan_bytecode(node, env, path):
  11   found = scan_ml_for_deps(node)
  12   bytecode_objects = map(lambda x: x + '.cmo', found)
  13   return bytecode_objects
  14 mlscan_bytecode = Scanner(function = mlfile_scan_bytecode, skeys = ['.ml'])

Ocaml Tool

This tool provides four builders :

which works like Object, Library and Program. You can customize command line options and choose between a bytecode executable, a native one or a toplevel (interactive loop).

OcamlPack is used for packing several object into one (see option -pack of ocamlc).

Ocaml Tool file :

   1 # Ocaml Tool
   2 # version: 0.2 (12/30/2005)
   3 #
   4 # TODO:
   5 # - testing
   6 # - make it portable (currently only tested on Unix)
   7 # - Lex and Yacc builders
   8 import os
   9 def read_command(cmd):
  10         """
  11         Execute the command cmd and return output
  12         """
  13         return os.popen(cmd).readlines()
  14 def when_code(env, bytecode, native, toplevel=None):
  15         """
  16         Return value depending on output code kind wanted
  17         """
  18         if toplevel == None:
  19                 toplevel = bytecode
  20         if env['OCAML_CODE'] == 'bytecode':
  21                 r = bytecode
  22         elif env['OCAML_CODE'] == 'native':
  23                 r = native
  24         elif env['OCAML_CODE'] == 'toplevel':
  25                 r = toplevel
  26         else:
  27                 print "$OCAML_CODE must be either 'toplevel', 'bytecode' or 'native'"
  28                 env.Exit(1)
  29         return r
  30 def obj_suffix(env):
  31         return when_code(env, '.cmo', '.cmx')
  32 def lib_suffix(env):
  33         return when_code(env, '.cma', '.cmxa')
  34 def set_suffix(f, suffix):
  35         b, e = os.path.splitext(str(f))
  36         return b+suffix
  37 def obj_of_src(f, env):
  38         return set_suffix(f, obj_suffix(env))
  39 def iface_of_src(f):
  40         return set_suffix(f, '.cmi')
  41 def comp(env):
  42         """
  43         Choose a compiler depending on environment variables
  44         """
  45         return when_code(env, '$OCAMLC', '$OCAMLOPT', '$OCAMLMKTOP')
  46 def flags(env, t=''):
  47         """
  48         Generate flags depending on environment variables
  49         """
  50         s = when_code(env, '$OCAMLC_FLAGS', '$OCAMLOPT_FLAGS', '$OCAMLMKTOP_FLAGS')
  51         if env['OCAML_DEBUG']:
  52                 s += when_code(env, ' -g', '')
  53         if env['OCAML_PROFILE']:
  54                 s += when_code(env, '', ' -p')
  55         if env['OCAML_PP']:
  56                 s += ' -pp %s' % env['OCAML_PP']
  57         if t == 'lib':
  58                 s += ' -a'
  59         elif t == 'obj':
  60                 s += ' -c'
  61         elif t == 'pack':
  62                 s += ' -pack'
  63         return s
  64 def is_installed(exe):
  65         """
  66         Return True if an executable is found in path
  67         """
  68         path = os.environ['PATH'].split(':')
  69         for p in path:
  70                 if os.path.exists( os.path.join(p, exe) ):
  71                         return True
  72         return False
  73 def norm_suffix(f, suffix):
  74         """
  75         Add a suffix if not present
  76         """
  77         p = str(f)
  78         e = p[-len(suffix):]
  79         if e != suffix:
  80                 p = p + suffix
  81         return p
  82 def norm_suffix_list(files, suffix):
  83         return map(lambda x: norm_suffix(x, suffix), files)
  84 def find_packages(env):
  85         """
  86         Use ocamlfind to retrieve libraries paths from package names
  87         """
  88         packs = env.Split(env['OCAML_PACKS'])
  89         if not is_installed(env['OCAMLFIND']):
  90                 if len(packs):
  91                         print "Warning: ocamlfind not found, ignoring ocaml packages"
  92                 return ""
  93         s = "%s query %%s -separator ' ' %s" % (
  94                 env['OCAMLFIND'], " ".join( packs)
  95         )
  96         i = read_command(s % '-i-format')
  97         l = read_command(s % '-l-format')
  98         code = when_code(env, 'byte', 'native')
  99         a = read_command(s % '-predicates %s -a-format' % code)
 100         r = " %s %s %s " % ( l[0][:-1], i[0][:-1], a[0][:-1] )
 101         return r
 102 def cleaner(files, env, lib=False):
 103         files = map(str, files)
 104         r = []
 105         for f in files:
 106                 r.append(obj_of_src(f, env))
 107                 r.append(iface_of_src(f))
 108                 if env['OCAML_CODE'] == 'native':
 109                         r.append(set_suffix(f, '.o'))
 110                         if lib:
 111                                 r.append(set_suffix(f, '.a'))
 112         return r
 113 def scanner(node, env, path):
 114         objs = norm_suffix_list(env['OCAML_OBJS'], obj_suffix(env))
 115         libs = norm_suffix_list(env['OCAML_LIBS'], lib_suffix(env))
 116         return libs+objs
 117 prog_scanner = lib_scanner = obj_scanner = pack_scanner = scanner
 118 def lib_emitter(target, source, env):
 119         t = norm_suffix(str(target[0]), lib_suffix(env))
 120         env.Clean(t, cleaner(source, env, lib=True))
 121         return (t, source)
 122 def obj_emitter(target, source, env):
 123         t = norm_suffix(str(target[0]), obj_suffix(env))
 124         env.Clean(t, cleaner(source+[t], env))
 125         return (t, source)
 126 def pack_emitter(target, source, env):
 127         t = norm_suffix(str(target[0]), obj_suffix(env))
 128         s = norm_suffix_list(source, obj_suffix(env))
 129         env.Clean(t, cleaner(source+[t], env))
 130         return (t, source)
 131 def prog_emitter(target, source, env):
 132         env.Clean(target, cleaner(source, env))
 133         return (target, source)
 134 def command_gen(source, target, env, comp, flags):
 135         """
 136         Generate command
 137         """
 138         target = str(target[0])
 139         objs = norm_suffix_list(env['OCAML_OBJS'], obj_suffix(env))
 140         objs = " ".join( objs )
 141         libs = norm_suffix_list(env['OCAML_LIBS'], lib_suffix(env))
 142         libs = " ".join( libs )
 143         inc = map(lambda x:'-I '+x, env['OCAML_PATH'])
 144         inc = " ".join(inc) + find_packages(env)
 145         s = ("%s %s -o %s %s %s %s $SOURCES" %
 146                         (comp, flags, target, inc, libs, objs)
 147         )
 148         return s
 149 def obj_gen(source, target, env, for_signature):
 150         return command_gen( source, target, env, comp(env), flags(env, 'obj'))
 151 def pack_gen(source, target, env, for_signature):
 152         return command_gen(source, target, env, comp(env), flags(env, 'pack'))
 153 def lib_gen(source, target, env, for_signature):
 154         return command_gen( source, target, env, comp(env), flags(env, 'lib'))
 155 def prog_gen(source, target, env, for_signature):
 156         return command_gen(source, target, env, comp(env), flags(env))
 157 def generate(env):
 158         """
 159         Add Builders and construction variables for Ocaml to an Environment.
 160         """
 161         prog_scan = env.Scanner(prog_scanner)
 162         lib_scan = env.Scanner(lib_scanner)
 163         obj_scan = env.Scanner(obj_scanner)
 164         pack_scan = env.Scanner(pack_scanner)
 165         env.Append(BUILDERS = {
 166                 'OcamlObject': env.Builder(
 167                         generator=obj_gen,
 168                         emitter=obj_emitter,
 169                         source_scanner=obj_scan
 170                 ),
 171                 # Pack several object into one object file
 172                 'OcamlPack': env.Builder(
 173                         generator=pack_gen,
 174                         emitter=pack_emitter,
 175                         source_scanner=pack_scan
 176                 ),
 177                 'OcamlLibrary': env.Builder(
 178                         generator=lib_gen,
 179                         emitter=lib_emitter,
 180                         source_scanner=lib_scan
 181                 ),
 182                 'OcamlProgram': env.Builder(
 183                         generator=prog_gen,
 184                         emitter=prog_emitter,
 185                         source_scanner=prog_scan
 186                 )
 187         })
 188         env.AppendUnique(
 189                 OCAMLC='ocamlc',
 190                 OCAMLOPT='ocamlopt',
 191                 OCAMLMKTOP='ocamlmktop',
 192                 OCAMLFIND='ocamlfind',
 193                 #OCAMLDEP='ocamldep',           # not used
 194                 OCAML_PP='',                            # not needed by default
 195                 OCAML_DEBUG=0,
 196                 OCAML_PROFILE=0,
 197                 OCAMLC_FLAGS='',
 198                 OCAMLOPT_FLAGS='',
 199                 OCAMLMKTOP_FLAGS='',
 200                 OCAML_LIBS=[],
 201                 OCAML_OBJS=[],
 202                 OCAML_PACKS=[],
 203                 OCAML_PATH=[],
 204                 OCAML_CODE=''                           # bytecode, toplevel or native
 205         )
 206 def exists(env):
 207         return env.Detect('ocaml')

There is three files : lib.ml, object.ml and prog.ml, we want to build the executable prog.

The dependencies are :

(* lib.ml *)
print_endline "lib.ml"

(* object.ml *)
open Lib;;
print_endline "object.ml"

(* prog.ml *)
open Graph.Builder;;
open Object;;
print_endline "hello ocaml world !";;

The SConstruct file :

   1 # SConstruct
   2 import ocaml
   3 env = Environment(
   4         OCAML_PACKS="ocamlgraph",
   5         OCAML_CODE='native',       # could be 'bytecode'
   6         OCAML_DEBUG=0,
   7         OCAML_PROFILE=0
   8 )
   9 env.Tool('ocaml', '.')
  10 o = env.OcamlObject('object', 'object.ml')
  11 l = env.OcamlLibrary('lib', 'lib.ml')
  12 env.OcamlProgram('prog', 'prog.ml', OCAML_LIBS=l, OCAML_OBJS=o)

This is the building process :

$ ls
lib.ml  object.ml  ocaml.py  ocaml.pyc  prog.ml  SConstruct
$ scons prog
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
ocamlopt -a -o lib.cmxa -ccopt -L/usr/lib/ocaml/3.08.3/ocamlgraph -I /usr/lib/ocaml/3.08.3/ocamlgraph lib.ml
ocamlopt -c -o object.cmx -ccopt -L/usr/lib/ocaml/3.08.3/ocamlgraph -I /usr/lib/ocaml/3.08.3/ocamlgraph object.ml
ocamlopt -o prog -ccopt -L/usr/lib/ocaml/3.08.3/ocamlgraph -I /usr/lib/ocaml/3.08.3/ocamlgraph lib.cmxa object.cmx prog.ml
scons: done building targets.
$ ls
lib.a    lib.cmxa  object.cmi  object.o   prog      prog.ml
lib.cmi  lib.ml    object.cmx  ocaml.py   prog.cmi  prog.o
lib.cmx  lib.o     object.ml   ocaml.pyc  prog.cmx  SConstruct
$ scons -c
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed lib.cmxa
Removed lib.cmx
Removed lib.cmi
Removed lib.o
Removed lib.a
Removed object.cmx
Removed object.cmi
Removed object.o
Removed prog
Removed prog.cmx
Removed prog.cmi
Removed prog.o
scons: done cleaning targets.
$ ./prog
lib.ml
object.ml
hello ocaml world !

OcamlBuilder (last edited 2010-04-22 15:40:31 by RusselWinder)