ocaml.py 9.3 KB

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