1#!/usr/bin/env python 2# encoding: utf-8 3# Thomas Nagy, 2006-2010 (ita) 4 5"ocaml support" 6 7import os, re 8from waflib import Utils, Task 9from waflib.Logs import error 10from waflib.TaskGen import feature, before_method, after_method, extension 11 12EXT_MLL = ['.mll'] 13EXT_MLY = ['.mly'] 14EXT_MLI = ['.mli'] 15EXT_MLC = ['.c'] 16EXT_ML = ['.ml'] 17 18open_re = re.compile(r'^\s*open\s+([a-zA-Z]+)(;;){0,1}$', re.M) 19foo = re.compile(r"""(\(\*)|(\*\))|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^()*"'\\]*)""", re.M) 20def 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 32def 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 58native_lst=['native', 'all', 'c_object'] 59bytecode_lst=['bytecode', 'all'] 60 61@feature('ocaml') 62def 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') 82def 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') 105def 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') 120def 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') 142def 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) 170def 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) 178def 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) 188def 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) 194def 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) 200def 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 214def 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 243class 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 250class 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 257class 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 263class 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 268class ocamllex(Task.Task): 269 """lexical generator""" 270 color = 'BLUE' 271 run_str = '${OCAMLLEX} ${SRC} -o ${TGT}' 272 before = ['ocamlcmi', 'ocaml', 'ocamlcc'] 273 274class 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 285def 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 317class 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 324class 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 331def 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 349