vala.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Ali Sabil, 2007
  4. # Radosław Szkodziński, 2010
  5. """
  6. At this point, vala is still unstable, so do not expect
  7. this tool to be too stable either (apis, etc)
  8. """
  9. import re
  10. from waflib import Build, Context, Errors, Logs, Node, Options, Task, Utils
  11. from waflib.TaskGen import extension, taskgen_method
  12. from waflib.Configure import conf
  13. class valac(Task.Task):
  14. """
  15. Compiles vala files
  16. """
  17. #run_str = "${VALAC} ${VALAFLAGS}" # ideally
  18. #vars = ['VALAC_VERSION']
  19. vars = ["VALAC", "VALAC_VERSION", "VALAFLAGS"]
  20. ext_out = ['.h']
  21. def run(self):
  22. cmd = self.env.VALAC + self.env.VALAFLAGS
  23. resources = getattr(self, 'vala_exclude', [])
  24. cmd.extend([a.abspath() for a in self.inputs if a not in resources])
  25. ret = self.exec_command(cmd, cwd=self.vala_dir_node.abspath())
  26. if ret:
  27. return ret
  28. if self.generator.dump_deps_node:
  29. self.generator.dump_deps_node.write('\n'.join(self.generator.packages))
  30. return ret
  31. @taskgen_method
  32. def init_vala_task(self):
  33. """
  34. Initializes the vala task with the relevant data (acts as a constructor)
  35. """
  36. self.profile = getattr(self, 'profile', 'gobject')
  37. self.packages = packages = Utils.to_list(getattr(self, 'packages', []))
  38. self.use = Utils.to_list(getattr(self, 'use', []))
  39. if packages and not self.use:
  40. self.use = packages[:] # copy
  41. if self.profile == 'gobject':
  42. if not 'GOBJECT' in self.use:
  43. self.use.append('GOBJECT')
  44. def addflags(flags):
  45. self.env.append_value('VALAFLAGS', flags)
  46. if self.profile:
  47. addflags('--profile=%s' % self.profile)
  48. valatask = self.valatask
  49. # output directory
  50. if hasattr(self, 'vala_dir'):
  51. if isinstance(self.vala_dir, str):
  52. valatask.vala_dir_node = self.path.get_bld().make_node(self.vala_dir)
  53. try:
  54. valatask.vala_dir_node.mkdir()
  55. except OSError:
  56. raise self.bld.fatal('Cannot create the vala dir %r' % valatask.vala_dir_node)
  57. else:
  58. valatask.vala_dir_node = self.vala_dir
  59. else:
  60. valatask.vala_dir_node = self.path.get_bld()
  61. addflags('--directory=%s' % valatask.vala_dir_node.abspath())
  62. if hasattr(self, 'thread'):
  63. if self.profile == 'gobject':
  64. if not 'GTHREAD' in self.use:
  65. self.use.append('GTHREAD')
  66. else:
  67. #Vala doesn't have threading support for dova nor posix
  68. Logs.warn('Profile %s means no threading support', self.profile)
  69. self.thread = False
  70. if self.thread:
  71. addflags('--thread')
  72. self.is_lib = 'cprogram' not in self.features
  73. if self.is_lib:
  74. addflags('--library=%s' % self.target)
  75. h_node = valatask.vala_dir_node.find_or_declare('%s.h' % self.target)
  76. valatask.outputs.append(h_node)
  77. addflags('--header=%s' % h_node.name)
  78. valatask.outputs.append(valatask.vala_dir_node.find_or_declare('%s.vapi' % self.target))
  79. if getattr(self, 'gir', None):
  80. gir_node = valatask.vala_dir_node.find_or_declare('%s.gir' % self.gir)
  81. addflags('--gir=%s' % gir_node.name)
  82. valatask.outputs.append(gir_node)
  83. self.vala_target_glib = getattr(self, 'vala_target_glib', getattr(Options.options, 'vala_target_glib', None))
  84. if self.vala_target_glib:
  85. addflags('--target-glib=%s' % self.vala_target_glib)
  86. addflags(['--define=%s' % x for x in Utils.to_list(getattr(self, 'vala_defines', []))])
  87. packages_private = Utils.to_list(getattr(self, 'packages_private', []))
  88. addflags(['--pkg=%s' % x for x in packages_private])
  89. def _get_api_version():
  90. api_version = '1.0'
  91. if hasattr(Context.g_module, 'API_VERSION'):
  92. version = Context.g_module.API_VERSION.split(".")
  93. if version[0] == "0":
  94. api_version = "0." + version[1]
  95. else:
  96. api_version = version[0] + ".0"
  97. return api_version
  98. self.includes = Utils.to_list(getattr(self, 'includes', []))
  99. valatask.install_path = getattr(self, 'install_path', '')
  100. valatask.vapi_path = getattr(self, 'vapi_path', '${DATAROOTDIR}/vala/vapi')
  101. valatask.pkg_name = getattr(self, 'pkg_name', self.env.PACKAGE)
  102. valatask.header_path = getattr(self, 'header_path', '${INCLUDEDIR}/%s-%s' % (valatask.pkg_name, _get_api_version()))
  103. valatask.install_binding = getattr(self, 'install_binding', True)
  104. self.vapi_dirs = vapi_dirs = Utils.to_list(getattr(self, 'vapi_dirs', []))
  105. #includes = []
  106. if hasattr(self, 'use'):
  107. local_packages = Utils.to_list(self.use)[:] # make sure to have a copy
  108. seen = []
  109. while len(local_packages) > 0:
  110. package = local_packages.pop()
  111. if package in seen:
  112. continue
  113. seen.append(package)
  114. # check if the package exists
  115. try:
  116. package_obj = self.bld.get_tgen_by_name(package)
  117. except Errors.WafError:
  118. continue
  119. # in practice the other task is already processed
  120. # but this makes it explicit
  121. package_obj.post()
  122. package_name = package_obj.target
  123. task = getattr(package_obj, 'valatask', None)
  124. if task:
  125. for output in task.outputs:
  126. if output.name == package_name + ".vapi":
  127. valatask.set_run_after(task)
  128. if package_name not in packages:
  129. packages.append(package_name)
  130. if output.parent not in vapi_dirs:
  131. vapi_dirs.append(output.parent)
  132. if output.parent not in self.includes:
  133. self.includes.append(output.parent)
  134. if hasattr(package_obj, 'use'):
  135. lst = self.to_list(package_obj.use)
  136. lst.reverse()
  137. local_packages = [pkg for pkg in lst if pkg not in seen] + local_packages
  138. addflags(['--pkg=%s' % p for p in packages])
  139. for vapi_dir in vapi_dirs:
  140. if isinstance(vapi_dir, Node.Node):
  141. v_node = vapi_dir
  142. else:
  143. v_node = self.path.find_dir(vapi_dir)
  144. if not v_node:
  145. Logs.warn('Unable to locate Vala API directory: %r', vapi_dir)
  146. else:
  147. addflags('--vapidir=%s' % v_node.abspath())
  148. self.dump_deps_node = None
  149. if self.is_lib and self.packages:
  150. self.dump_deps_node = valatask.vala_dir_node.find_or_declare('%s.deps' % self.target)
  151. valatask.outputs.append(self.dump_deps_node)
  152. if self.is_lib and valatask.install_binding:
  153. headers_list = [o for o in valatask.outputs if o.suffix() == ".h"]
  154. if headers_list:
  155. self.install_vheader = self.add_install_files(install_to=valatask.header_path, install_from=headers_list)
  156. vapi_list = [o for o in valatask.outputs if (o.suffix() in (".vapi", ".deps"))]
  157. if vapi_list:
  158. self.install_vapi = self.add_install_files(install_to=valatask.vapi_path, install_from=vapi_list)
  159. gir_list = [o for o in valatask.outputs if o.suffix() == '.gir']
  160. if gir_list:
  161. self.install_gir = self.add_install_files(
  162. install_to=getattr(self, 'gir_path', '${DATAROOTDIR}/gir-1.0'), install_from=gir_list)
  163. if hasattr(self, 'vala_resources'):
  164. nodes = self.to_nodes(self.vala_resources)
  165. valatask.vala_exclude = getattr(valatask, 'vala_exclude', []) + nodes
  166. valatask.inputs.extend(nodes)
  167. for x in nodes:
  168. addflags(['--gresources', x.abspath()])
  169. @extension('.vala', '.gs')
  170. def vala_file(self, node):
  171. """
  172. Compile a vala file and bind the task to *self.valatask*. If an existing vala task is already set, add the node
  173. to its inputs. The typical example is::
  174. def build(bld):
  175. bld.program(
  176. packages = 'gtk+-2.0',
  177. target = 'vala-gtk-example',
  178. use = 'GTK GLIB',
  179. source = 'vala-gtk-example.vala foo.vala',
  180. vala_defines = ['DEBUG'] # adds --define=<xyz> values to the command-line
  181. # the following arguments are for libraries
  182. #gir = 'hello-1.0',
  183. #gir_path = '/tmp',
  184. #vapi_path = '/tmp',
  185. #pkg_name = 'hello'
  186. # disable installing of gir, vapi and header
  187. #install_binding = False
  188. # profile = 'xyz' # adds --profile=<xyz> to enable profiling
  189. # thread = True, # adds --thread, except if profile is on or not on 'gobject'
  190. # vala_target_glib = 'xyz' # adds --target-glib=<xyz>, can be given through the command-line option --vala-target-glib=<xyz>
  191. )
  192. :param node: vala file
  193. :type node: :py:class:`waflib.Node.Node`
  194. """
  195. try:
  196. valatask = self.valatask
  197. except AttributeError:
  198. valatask = self.valatask = self.create_task('valac')
  199. self.init_vala_task()
  200. valatask.inputs.append(node)
  201. name = node.name[:node.name.rfind('.')] + '.c'
  202. c_node = valatask.vala_dir_node.find_or_declare(name)
  203. valatask.outputs.append(c_node)
  204. self.source.append(c_node)
  205. @extension('.vapi')
  206. def vapi_file(self, node):
  207. try:
  208. valatask = self.valatask
  209. except AttributeError:
  210. valatask = self.valatask = self.create_task('valac')
  211. self.init_vala_task()
  212. valatask.inputs.append(node)
  213. @conf
  214. def find_valac(self, valac_name, min_version):
  215. """
  216. Find the valac program, and execute it to store the version
  217. number in *conf.env.VALAC_VERSION*
  218. :param valac_name: program name
  219. :type valac_name: string or list of string
  220. :param min_version: minimum version acceptable
  221. :type min_version: tuple of int
  222. """
  223. valac = self.find_program(valac_name, var='VALAC')
  224. try:
  225. output = self.cmd_and_log(valac + ['--version'])
  226. except Errors.WafError:
  227. valac_version = None
  228. else:
  229. ver = re.search(r'\d+.\d+.\d+', output).group().split('.')
  230. valac_version = tuple([int(x) for x in ver])
  231. self.msg('Checking for %s version >= %r' % (valac_name, min_version),
  232. valac_version, valac_version and valac_version >= min_version)
  233. if valac and valac_version < min_version:
  234. self.fatal("%s version %r is too old, need >= %r" % (valac_name, valac_version, min_version))
  235. self.env.VALAC_VERSION = valac_version
  236. return valac
  237. @conf
  238. def check_vala(self, min_version=(0,8,0), branch=None):
  239. """
  240. Check if vala compiler from a given branch exists of at least a given
  241. version.
  242. :param min_version: minimum version acceptable (0.8.0)
  243. :type min_version: tuple
  244. :param branch: first part of the version number, in case a snapshot is used (0, 8)
  245. :type branch: tuple of int
  246. """
  247. if self.env.VALA_MINVER:
  248. min_version = self.env.VALA_MINVER
  249. if self.env.VALA_MINVER_BRANCH:
  250. branch = self.env.VALA_MINVER_BRANCH
  251. if not branch:
  252. branch = min_version[:2]
  253. try:
  254. find_valac(self, 'valac-%d.%d' % (branch[0], branch[1]), min_version)
  255. except self.errors.ConfigurationError:
  256. find_valac(self, 'valac', min_version)
  257. @conf
  258. def check_vala_deps(self):
  259. """
  260. Load the gobject and gthread packages if they are missing.
  261. """
  262. if not self.env.HAVE_GOBJECT:
  263. pkg_args = {'package': 'gobject-2.0',
  264. 'uselib_store': 'GOBJECT',
  265. 'args': '--cflags --libs'}
  266. if getattr(Options.options, 'vala_target_glib', None):
  267. pkg_args['atleast_version'] = Options.options.vala_target_glib
  268. self.check_cfg(**pkg_args)
  269. if not self.env.HAVE_GTHREAD:
  270. pkg_args = {'package': 'gthread-2.0',
  271. 'uselib_store': 'GTHREAD',
  272. 'args': '--cflags --libs'}
  273. if getattr(Options.options, 'vala_target_glib', None):
  274. pkg_args['atleast_version'] = Options.options.vala_target_glib
  275. self.check_cfg(**pkg_args)
  276. def configure(self):
  277. """
  278. Use the following to enforce minimum vala version::
  279. def configure(conf):
  280. conf.env.VALA_MINVER = (0, 10, 0)
  281. conf.load('vala')
  282. """
  283. self.load('gnu_dirs')
  284. self.check_vala_deps()
  285. self.check_vala()
  286. self.add_os_flags('VALAFLAGS')
  287. self.env.append_unique('VALAFLAGS', ['-C'])
  288. def options(opt):
  289. """
  290. Load the :py:mod:`waflib.Tools.gnu_dirs` tool and add the ``--vala-target-glib`` command-line option
  291. """
  292. opt.load('gnu_dirs')
  293. valaopts = opt.add_option_group('Vala Compiler Options')
  294. valaopts.add_option('--vala-target-glib', default=None,
  295. dest='vala_target_glib', metavar='MAJOR.MINOR',
  296. help='Target version of glib for Vala GObject code generation')