Include waf as an extracted source directory, rather than as a one-in-a-file script.
[samba.git] / buildtools / wafadmin / Tools / ocaml.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2006 (ita)
4
5 "ocaml support"
6
7 import os, re
8 import TaskGen, Utils, Task, Build
9 from Logs import error
10 from TaskGen import taskgen, feature, before, after, extension
11
12 EXT_MLL = ['.mll']
13 EXT_MLY = ['.mly']
14 EXT_MLI = ['.mli']
15 EXT_MLC = ['.c']
16 EXT_ML  = ['.ml']
17
18 open_re = re.compile('^\s*open\s+([a-zA-Z]+)(;;){0,1}$', re.M)
19 foo = re.compile(r"""(\(\*)|(\*\))|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^()*"'\\]*)""", re.M)
20 def filter_comments(txt):
21         meh = [0]
22         def repl(m):
23                 if m.group(1): meh[0] += 1
24                 elif m.group(2): meh[0] -= 1
25                 elif not meh[0]: return m.group(0)
26                 return ''
27         return foo.sub(repl, txt)
28
29 def scan(self):
30         node = self.inputs[0]
31         code = filter_comments(node.read(self.env))
32
33         global open_re
34         names = []
35         import_iterator = open_re.finditer(code)
36         if import_iterator:
37                 for import_match in import_iterator:
38                         names.append(import_match.group(1))
39         found_lst = []
40         raw_lst = []
41         for name in names:
42                 nd = None
43                 for x in self.incpaths:
44                         nd = x.find_resource(name.lower()+'.ml')
45                         if not nd: nd = x.find_resource(name+'.ml')
46                         if nd:
47                                 found_lst.append(nd)
48                                 break
49                 else:
50                         raw_lst.append(name)
51
52         return (found_lst, raw_lst)
53
54 native_lst=['native', 'all', 'c_object']
55 bytecode_lst=['bytecode', 'all']
56 class ocaml_taskgen(TaskGen.task_gen):
57         def __init__(self, *k, **kw):
58                 TaskGen.task_gen.__init__(self, *k, **kw)
59
60 @feature('ocaml')
61 def init_ml(self):
62         Utils.def_attrs(self,
63                 type = 'all',
64                 incpaths_lst = [],
65                 bld_incpaths_lst = [],
66                 mlltasks = [],
67                 mlytasks = [],
68                 mlitasks = [],
69                 native_tasks = [],
70                 bytecode_tasks = [],
71                 linktasks = [],
72                 bytecode_env = None,
73                 native_env = None,
74                 compiled_tasks = [],
75                 includes = '',
76                 uselib = '',
77                 are_deps_set = 0)
78
79 @feature('ocaml')
80 @after('init_ml')
81 def init_envs_ml(self):
82
83         self.islibrary = getattr(self, 'islibrary', False)
84
85         global native_lst, bytecode_lst
86         self.native_env = None
87         if self.type in native_lst:
88                 self.native_env = self.env.copy()
89                 if self.islibrary: self.native_env['OCALINKFLAGS']   = '-a'
90
91         self.bytecode_env = None
92         if self.type in bytecode_lst:
93                 self.bytecode_env = self.env.copy()
94                 if self.islibrary: self.bytecode_env['OCALINKFLAGS'] = '-a'
95
96         if self.type == 'c_object':
97                 self.native_env.append_unique('OCALINKFLAGS_OPT', '-output-obj')
98
99 @feature('ocaml')
100 @before('apply_vars_ml')
101 @after('init_envs_ml')
102 def apply_incpaths_ml(self):
103         inc_lst = self.includes.split()
104         lst = self.incpaths_lst
105         for dir in inc_lst:
106                 node = self.path.find_dir(dir)
107                 if not node:
108                         error("node not found: " + str(dir))
109                         continue
110                 self.bld.rescan(node)
111                 if not node in lst: lst.append(node)
112                 self.bld_incpaths_lst.append(node)
113         # now the nodes are added to self.incpaths_lst
114
115 @feature('ocaml')
116 @before('apply_core')
117 def apply_vars_ml(self):
118         for i in self.incpaths_lst:
119                 if self.bytecode_env:
120                         app = self.bytecode_env.append_value
121                         app('OCAMLPATH', '-I')
122                         app('OCAMLPATH', i.srcpath(self.env))
123                         app('OCAMLPATH', '-I')
124                         app('OCAMLPATH', i.bldpath(self.env))
125
126                 if self.native_env:
127                         app = self.native_env.append_value
128                         app('OCAMLPATH', '-I')
129                         app('OCAMLPATH', i.bldpath(self.env))
130                         app('OCAMLPATH', '-I')
131                         app('OCAMLPATH', i.srcpath(self.env))
132
133         varnames = ['INCLUDES', 'OCAMLFLAGS', 'OCALINKFLAGS', 'OCALINKFLAGS_OPT']
134         for name in self.uselib.split():
135                 for vname in varnames:
136                         cnt = self.env[vname+'_'+name]
137                         if cnt:
138                                 if self.bytecode_env: self.bytecode_env.append_value(vname, cnt)
139                                 if self.native_env: self.native_env.append_value(vname, cnt)
140
141 @feature('ocaml')
142 @after('apply_core')
143 def apply_link_ml(self):
144
145         if self.bytecode_env:
146                 ext = self.islibrary and '.cma' or '.run'
147
148                 linktask = self.create_task('ocalink')
149                 linktask.bytecode = 1
150                 linktask.set_outputs(self.path.find_or_declare(self.target + ext))
151                 linktask.obj = self
152                 linktask.env = self.bytecode_env
153                 self.linktasks.append(linktask)
154
155         if self.native_env:
156                 if self.type == 'c_object': ext = '.o'
157                 elif self.islibrary: ext = '.cmxa'
158                 else: ext = ''
159
160                 linktask = self.create_task('ocalinkx')
161                 linktask.set_outputs(self.path.find_or_declare(self.target + ext))
162                 linktask.obj = self
163                 linktask.env = self.native_env
164                 self.linktasks.append(linktask)
165
166                 # we produce a .o file to be used by gcc
167                 self.compiled_tasks.append(linktask)
168
169 @extension(EXT_MLL)
170 def mll_hook(self, node):
171         mll_task = self.create_task('ocamllex', node, node.change_ext('.ml'), env=self.native_env)
172         self.mlltasks.append(mll_task)
173
174         self.allnodes.append(mll_task.outputs[0])
175
176 @extension(EXT_MLY)
177 def mly_hook(self, node):
178         mly_task = self.create_task('ocamlyacc', node, [node.change_ext('.ml'), node.change_ext('.mli')], env=self.native_env)
179         self.mlytasks.append(mly_task)
180         self.allnodes.append(mly_task.outputs[0])
181
182         task = self.create_task('ocamlcmi', mly_task.outputs[1], mly_task.outputs[1].change_ext('.cmi'), env=self.native_env)
183
184 @extension(EXT_MLI)
185 def mli_hook(self, node):
186         task = self.create_task('ocamlcmi', node, node.change_ext('.cmi'), env=self.native_env)
187         self.mlitasks.append(task)
188
189 @extension(EXT_MLC)
190 def mlc_hook(self, node):
191         task = self.create_task('ocamlcc', node, node.change_ext('.o'), env=self.native_env)
192         self.compiled_tasks.append(task)
193
194 @extension(EXT_ML)
195 def ml_hook(self, node):
196         if self.native_env:
197                 task = self.create_task('ocamlx', node, node.change_ext('.cmx'), env=self.native_env)
198                 task.obj = self
199                 task.incpaths = self.bld_incpaths_lst
200                 self.native_tasks.append(task)
201
202         if self.bytecode_env:
203                 task = self.create_task('ocaml', node, node.change_ext('.cmo'), env=self.bytecode_env)
204                 task.obj = self
205                 task.bytecode = 1
206                 task.incpaths = self.bld_incpaths_lst
207                 self.bytecode_tasks.append(task)
208
209 def compile_may_start(self):
210         if not getattr(self, 'flag_deps', ''):
211                 self.flag_deps = 1
212
213                 # the evil part is that we can only compute the dependencies after the
214                 # source files can be read (this means actually producing the source files)
215                 if getattr(self, 'bytecode', ''): alltasks = self.obj.bytecode_tasks
216                 else: alltasks = self.obj.native_tasks
217
218                 self.signature() # ensure that files are scanned - unfortunately
219                 tree = self.generator.bld
220                 env = self.env
221                 for node in self.inputs:
222                         lst = tree.node_deps[self.unique_id()]
223                         for depnode in lst:
224                                 for t in alltasks:
225                                         if t == self: continue
226                                         if depnode in t.inputs:
227                                                 self.set_run_after(t)
228
229                 # TODO necessary to get the signature right - for now
230                 delattr(self, 'cache_sig')
231                 self.signature()
232
233         return Task.Task.runnable_status(self)
234
235 b = Task.simple_task_type
236 cls = b('ocamlx', '${OCAMLOPT} ${OCAMLPATH} ${OCAMLFLAGS} ${INCLUDES} -c -o ${TGT} ${SRC}', color='GREEN', shell=False)
237 cls.runnable_status = compile_may_start
238 cls.scan = scan
239
240 b = Task.simple_task_type
241 cls = b('ocaml', '${OCAMLC} ${OCAMLPATH} ${OCAMLFLAGS} ${INCLUDES} -c -o ${TGT} ${SRC}', color='GREEN', shell=False)
242 cls.runnable_status = compile_may_start
243 cls.scan = scan
244
245
246 b('ocamlcmi', '${OCAMLC} ${OCAMLPATH} ${INCLUDES} -o ${TGT} -c ${SRC}', color='BLUE', before="ocaml ocamlcc ocamlx")
247 b('ocamlcc', 'cd ${TGT[0].bld_dir(env)} && ${OCAMLOPT} ${OCAMLFLAGS} ${OCAMLPATH} ${INCLUDES} -c ${SRC[0].abspath(env)}', color='GREEN')
248
249 b('ocamllex', '${OCAMLLEX} ${SRC} -o ${TGT}', color='BLUE', before="ocamlcmi ocaml ocamlcc")
250 b('ocamlyacc', '${OCAMLYACC} -b ${TGT[0].bld_base(env)} ${SRC}', color='BLUE', before="ocamlcmi ocaml ocamlcc")
251
252
253 def link_may_start(self):
254         if not getattr(self, 'order', ''):
255
256                 # now reorder the inputs given the task dependencies
257                 if getattr(self, 'bytecode', 0): alltasks = self.obj.bytecode_tasks
258                 else: alltasks = self.obj.native_tasks
259
260                 # this part is difficult, we do not have a total order on the tasks
261                 # if the dependencies are wrong, this may not stop
262                 seen = []
263                 pendant = []+alltasks
264                 while pendant:
265                         task = pendant.pop(0)
266                         if task in seen: continue
267                         for x in task.run_after:
268                                 if not x in seen:
269                                         pendant.append(task)
270                                         break
271                         else:
272                                 seen.append(task)
273                 self.inputs = [x.outputs[0] for x in seen]
274                 self.order = 1
275         return Task.Task.runnable_status(self)
276
277 act = b('ocalink', '${OCAMLC} -o ${TGT} ${INCLUDES} ${OCALINKFLAGS} ${SRC}', color='YELLOW', after="ocaml ocamlcc")
278 act.runnable_status = link_may_start
279 act = b('ocalinkx', '${OCAMLOPT} -o ${TGT} ${INCLUDES} ${OCALINKFLAGS_OPT} ${SRC}', color='YELLOW', after="ocamlx ocamlcc")
280 act.runnable_status = link_may_start
281
282 def detect(conf):
283         opt = conf.find_program('ocamlopt', var='OCAMLOPT')
284         occ = conf.find_program('ocamlc', var='OCAMLC')
285         if (not opt) or (not occ):
286                 conf.fatal('The objective caml compiler was not found:\ninstall it or make it available in your PATH')
287
288         v = conf.env
289         v['OCAMLC']       = occ
290         v['OCAMLOPT']     = opt
291         v['OCAMLLEX']     = conf.find_program('ocamllex', var='OCAMLLEX')
292         v['OCAMLYACC']    = conf.find_program('ocamlyacc', var='OCAMLYACC')
293         v['OCAMLFLAGS']   = ''
294         v['OCAMLLIB']     = Utils.cmd_output(conf.env['OCAMLC']+' -where').strip()+os.sep
295         v['LIBPATH_OCAML'] = Utils.cmd_output(conf.env['OCAMLC']+' -where').strip()+os.sep
296         v['CPPPATH_OCAML'] = Utils.cmd_output(conf.env['OCAMLC']+' -where').strip()+os.sep
297         v['LIB_OCAML'] = 'camlrun'
298