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 |
ocamlc generates .cmi files from .mli files, and .cmo files from .ml files
ocamlopt generates .cmi files from .mli files and .cmx and .o files from .ml files
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 :
- prog: object.cmx lib.cmxa prog.ml
- object.cmx: object.ml lib.cmxa
- lib.cmxa: lib.ml
(* 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 !
