jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
9.2KB

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