third_party/waf: upgrade to waf 2.0.8
[bbaumbach/samba-autobuild/.git] / third_party / waf / waflib / extras / ocaml.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2006-2010 (ita)
4
5 "ocaml support"
6
7 import os, re
8 from waflib import Utils, Task
9 from waflib.Logs import error
10 from waflib.TaskGen import feature, before_method, after_method, 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):
24                         meh[0] += 1
25                 elif m.group(2):
26                         meh[0] -= 1
27                 elif not meh[0]:
28                         return m.group()
29                 return ''
30         return foo.sub(repl, txt)
31
32 def scan(self):
33         node = self.inputs[0]
34         code = filter_comments(node.read())
35
36         global open_re
37         names = []
38         import_iterator = open_re.finditer(code)
39         if import_iterator:
40                 for import_match in import_iterator:
41                         names.append(import_match.group(1))
42         found_lst = []
43         raw_lst = []
44         for name in names:
45                 nd = None
46                 for x in self.incpaths:
47                         nd = x.find_resource(name.lower()+'.ml')
48                         if not nd:
49                                 nd = x.find_resource(name+'.ml')
50                         if nd:
51                                 found_lst.append(nd)
52                                 break
53                 else:
54                         raw_lst.append(name)
55
56         return (found_lst, raw_lst)
57
58 native_lst=['native', 'all', 'c_object']
59 bytecode_lst=['bytecode', 'all']
60
61 @feature('ocaml')
62 def init_ml(self):
63         Utils.def_attrs(self,
64                 type = 'all',
65                 incpaths_lst = [],
66                 bld_incpaths_lst = [],
67                 mlltasks = [],
68                 mlytasks = [],
69                 mlitasks = [],
70                 native_tasks = [],
71                 bytecode_tasks = [],
72                 linktasks = [],
73                 bytecode_env = None,
74                 native_env = None,
75                 compiled_tasks = [],
76                 includes = '',
77                 uselib = '',
78                 are_deps_set = 0)
79
80 @feature('ocaml')
81 @after_method('init_ml')
82 def init_envs_ml(self):
83
84         self.islibrary = getattr(self, 'islibrary', False)
85
86         global native_lst, bytecode_lst
87         self.native_env = None
88         if self.type in native_lst:
89                 self.native_env = self.env.derive()
90                 if self.islibrary:
91                         self.native_env['OCALINKFLAGS']   = '-a'
92
93         self.bytecode_env = None
94         if self.type in bytecode_lst:
95                 self.bytecode_env = self.env.derive()
96                 if self.islibrary:
97                         self.bytecode_env['OCALINKFLAGS'] = '-a'
98
99         if self.type == 'c_object':
100                 self.native_env.append_unique('OCALINKFLAGS_OPT', '-output-obj')
101
102 @feature('ocaml')
103 @before_method('apply_vars_ml')
104 @after_method('init_envs_ml')
105 def apply_incpaths_ml(self):
106         inc_lst = self.includes.split()
107         lst = self.incpaths_lst
108         for dir in inc_lst:
109                 node = self.path.find_dir(dir)
110                 if not node:
111                         error("node not found: " + str(dir))
112                         continue
113                 if not node in lst:
114                         lst.append(node)
115                 self.bld_incpaths_lst.append(node)
116         # now the nodes are added to self.incpaths_lst
117
118 @feature('ocaml')
119 @before_method('process_source')
120 def apply_vars_ml(self):
121         for i in self.incpaths_lst:
122                 if self.bytecode_env:
123                         app = self.bytecode_env.append_value
124                         app('OCAMLPATH', ['-I', i.bldpath(), '-I', i.srcpath()])
125
126                 if self.native_env:
127                         app = self.native_env.append_value
128                         app('OCAMLPATH', ['-I', i.bldpath(), '-I', i.srcpath()])
129
130         varnames = ['INCLUDES', 'OCAMLFLAGS', 'OCALINKFLAGS', 'OCALINKFLAGS_OPT']
131         for name in self.uselib.split():
132                 for vname in varnames:
133                         cnt = self.env[vname+'_'+name]
134                         if cnt:
135                                 if self.bytecode_env:
136                                         self.bytecode_env.append_value(vname, cnt)
137                                 if self.native_env:
138                                         self.native_env.append_value(vname, cnt)
139
140 @feature('ocaml')
141 @after_method('process_source')
142 def apply_link_ml(self):
143
144         if self.bytecode_env:
145                 ext = self.islibrary and '.cma' or '.run'
146
147                 linktask = self.create_task('ocalink')
148                 linktask.bytecode = 1
149                 linktask.set_outputs(self.path.find_or_declare(self.target + ext))
150                 linktask.env = self.bytecode_env
151                 self.linktasks.append(linktask)
152
153         if self.native_env:
154                 if self.type == 'c_object':
155                         ext = '.o'
156                 elif self.islibrary:
157                         ext = '.cmxa'
158                 else:
159                         ext = ''
160
161                 linktask = self.create_task('ocalinkx')
162                 linktask.set_outputs(self.path.find_or_declare(self.target + ext))
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'))
172         mll_task.env = self.native_env.derive()
173         self.mlltasks.append(mll_task)
174
175         self.source.append(mll_task.outputs[0])
176
177 @extension(*EXT_MLY)
178 def mly_hook(self, node):
179         mly_task = self.create_task('ocamlyacc', node, [node.change_ext('.ml'), node.change_ext('.mli')])
180         mly_task.env = self.native_env.derive()
181         self.mlytasks.append(mly_task)
182         self.source.append(mly_task.outputs[0])
183
184         task = self.create_task('ocamlcmi', mly_task.outputs[1], mly_task.outputs[1].change_ext('.cmi'))
185         task.env = self.native_env.derive()
186
187 @extension(*EXT_MLI)
188 def mli_hook(self, node):
189         task = self.create_task('ocamlcmi', node, node.change_ext('.cmi'))
190         task.env = self.native_env.derive()
191         self.mlitasks.append(task)
192
193 @extension(*EXT_MLC)
194 def mlc_hook(self, node):
195         task = self.create_task('ocamlcc', node, node.change_ext('.o'))
196         task.env = self.native_env.derive()
197         self.compiled_tasks.append(task)
198
199 @extension(*EXT_ML)
200 def ml_hook(self, node):
201         if self.native_env:
202                 task = self.create_task('ocamlx', node, node.change_ext('.cmx'))
203                 task.env = self.native_env.derive()
204                 task.incpaths = self.bld_incpaths_lst
205                 self.native_tasks.append(task)
206
207         if self.bytecode_env:
208                 task = self.create_task('ocaml', node, node.change_ext('.cmo'))
209                 task.env = self.bytecode_env.derive()
210                 task.bytecode = 1
211                 task.incpaths = self.bld_incpaths_lst
212                 self.bytecode_tasks.append(task)
213
214 def compile_may_start(self):
215
216         if not getattr(self, 'flag_deps', ''):
217                 self.flag_deps = 1
218
219                 # the evil part is that we can only compute the dependencies after the
220                 # source files can be read (this means actually producing the source files)
221                 if getattr(self, 'bytecode', ''):
222                         alltasks = self.generator.bytecode_tasks
223                 else:
224                         alltasks = self.generator.native_tasks
225
226                 self.signature() # ensure that files are scanned - unfortunately
227                 tree = self.generator.bld
228                 for node in self.inputs:
229                         lst = tree.node_deps[self.uid()]
230                         for depnode in lst:
231                                 for t in alltasks:
232                                         if t == self:
233                                                 continue
234                                         if depnode in t.inputs:
235                                                 self.set_run_after(t)
236
237                 # TODO necessary to get the signature right - for now
238                 delattr(self, 'cache_sig')
239                 self.signature()
240
241         return Task.Task.runnable_status(self)
242
243 class ocamlx(Task.Task):
244         """native caml compilation"""
245         color   = 'GREEN'
246         run_str = '${OCAMLOPT} ${OCAMLPATH} ${OCAMLFLAGS} ${OCAMLINCLUDES} -c -o ${TGT} ${SRC}'
247         scan    = scan
248         runnable_status = compile_may_start
249
250 class ocaml(Task.Task):
251         """bytecode caml compilation"""
252         color   = 'GREEN'
253         run_str = '${OCAMLC} ${OCAMLPATH} ${OCAMLFLAGS} ${OCAMLINCLUDES} -c -o ${TGT} ${SRC}'
254         scan    = scan
255         runnable_status = compile_may_start
256
257 class ocamlcmi(Task.Task):
258         """interface generator (the .i files?)"""
259         color   = 'BLUE'
260         run_str = '${OCAMLC} ${OCAMLPATH} ${OCAMLINCLUDES} -o ${TGT} -c ${SRC}'
261         before  = ['ocamlcc', 'ocaml', 'ocamlcc']
262
263 class ocamlcc(Task.Task):
264         """ocaml to c interfaces"""
265         color   = 'GREEN'
266         run_str = 'cd ${TGT[0].bld_dir()} && ${OCAMLOPT} ${OCAMLFLAGS} ${OCAMLPATH} ${OCAMLINCLUDES} -c ${SRC[0].abspath()}'
267
268 class ocamllex(Task.Task):
269         """lexical generator"""
270         color   = 'BLUE'
271         run_str = '${OCAMLLEX} ${SRC} -o ${TGT}'
272         before  = ['ocamlcmi', 'ocaml', 'ocamlcc']
273
274 class ocamlyacc(Task.Task):
275         """parser generator"""
276         color   = 'BLUE'
277         run_str = '${OCAMLYACC} -b ${tsk.base()} ${SRC}'
278         before  = ['ocamlcmi', 'ocaml', 'ocamlcc']
279
280         def base(self):
281                 node = self.outputs[0]
282                 s = os.path.splitext(node.name)[0]
283                 return node.bld_dir() + os.sep + s
284
285 def link_may_start(self):
286
287         if getattr(self, 'bytecode', 0):
288                 alltasks = self.generator.bytecode_tasks
289         else:
290                 alltasks = self.generator.native_tasks
291
292         for x in alltasks:
293                 if not x.hasrun:
294                         return Task.ASK_LATER
295
296         if not getattr(self, 'order', ''):
297
298                 # now reorder the inputs given the task dependencies
299                 # this part is difficult, we do not have a total order on the tasks
300                 # if the dependencies are wrong, this may not stop
301                 seen = []
302                 pendant = []+alltasks
303                 while pendant:
304                         task = pendant.pop(0)
305                         if task in seen:
306                                 continue
307                         for x in task.run_after:
308                                 if not x in seen:
309                                         pendant.append(task)
310                                         break
311                         else:
312                                 seen.append(task)
313                 self.inputs = [x.outputs[0] for x in seen]
314                 self.order = 1
315         return Task.Task.runnable_status(self)
316
317 class ocalink(Task.Task):
318         """bytecode caml link"""
319         color   = 'YELLOW'
320         run_str = '${OCAMLC} -o ${TGT} ${OCAMLINCLUDES} ${OCALINKFLAGS} ${SRC}'
321         runnable_status = link_may_start
322         after = ['ocaml', 'ocamlcc']
323
324 class ocalinkx(Task.Task):
325         """native caml link"""
326         color   = 'YELLOW'
327         run_str = '${OCAMLOPT} -o ${TGT} ${OCAMLINCLUDES} ${OCALINKFLAGS_OPT} ${SRC}'
328         runnable_status = link_may_start
329         after = ['ocamlx', 'ocamlcc']
330
331 def configure(conf):
332         opt = conf.find_program('ocamlopt', var='OCAMLOPT', mandatory=False)
333         occ = conf.find_program('ocamlc', var='OCAMLC', mandatory=False)
334         if (not opt) or (not occ):
335                 conf.fatal('The objective caml compiler was not found:\ninstall it or make it available in your PATH')
336
337         v = conf.env
338         v['OCAMLC']       = occ
339         v['OCAMLOPT']     = opt
340         v['OCAMLLEX']     = conf.find_program('ocamllex', var='OCAMLLEX', mandatory=False)
341         v['OCAMLYACC']    = conf.find_program('ocamlyacc', var='OCAMLYACC', mandatory=False)
342         v['OCAMLFLAGS']   = ''
343         where = conf.cmd_and_log(conf.env.OCAMLC + ['-where']).strip()+os.sep
344         v['OCAMLLIB']     = where
345         v['LIBPATH_OCAML'] = where
346         v['INCLUDES_OCAML'] = where
347         v['LIB_OCAML'] = 'camlrun'
348