1#! /usr/bin/env python
2# encoding: utf-8
3# Thomas Nagy, 2006-2018 (ita)
4
5"""
6Support for GLib2 tools:
7
8* marshal
9* enums
10* gsettings
11* gresource
12"""
13
14import os
15import functools
16from waflib import Context, Task, Utils, Options, Errors, Logs
17from waflib.TaskGen import taskgen_method, before_method, feature, extension
18from waflib.Configure import conf
19
20################## marshal files
21
22@taskgen_method
23def add_marshal_file(self, filename, prefix):
24	"""
25	Adds a file to the list of marshal files to process. Store them in the attribute *marshal_list*.
26
27	:param filename: xml file to compile
28	:type filename: string
29	:param prefix: marshal prefix (--prefix=prefix)
30	:type prefix: string
31	"""
32	if not hasattr(self, 'marshal_list'):
33		self.marshal_list = []
34	self.meths.append('process_marshal')
35	self.marshal_list.append((filename, prefix))
36
37@before_method('process_source')
38def process_marshal(self):
39	"""
40	Processes the marshal files stored in the attribute *marshal_list* to create :py:class:`waflib.Tools.glib2.glib_genmarshal` instances.
41	Adds the c file created to the list of source to process.
42	"""
43	for f, prefix in getattr(self, 'marshal_list', []):
44		node = self.path.find_resource(f)
45
46		if not node:
47			raise Errors.WafError('file not found %r' % f)
48
49		h_node = node.change_ext('.h')
50		c_node = node.change_ext('.c')
51
52		task = self.create_task('glib_genmarshal', node, [h_node, c_node])
53		task.env.GLIB_GENMARSHAL_PREFIX = prefix
54	self.source = self.to_nodes(getattr(self, 'source', []))
55	self.source.append(c_node)
56
57class glib_genmarshal(Task.Task):
58	vars    = ['GLIB_GENMARSHAL_PREFIX', 'GLIB_GENMARSHAL']
59	color   = 'BLUE'
60	ext_out = ['.h']
61	def run(self):
62		bld = self.generator.bld
63
64		get = self.env.get_flat
65		cmd1 = "%s %s --prefix=%s --header > %s" % (
66			get('GLIB_GENMARSHAL'),
67			self.inputs[0].srcpath(),
68			get('GLIB_GENMARSHAL_PREFIX'),
69			self.outputs[0].abspath()
70		)
71
72		ret = bld.exec_command(cmd1)
73		if ret:
74			return ret
75
76		#print self.outputs[1].abspath()
77		c = '''#include "%s"\n''' % self.outputs[0].name
78		self.outputs[1].write(c)
79
80		cmd2 = "%s %s --prefix=%s --body >> %s" % (
81			get('GLIB_GENMARSHAL'),
82			self.inputs[0].srcpath(),
83			get('GLIB_GENMARSHAL_PREFIX'),
84			self.outputs[1].abspath()
85		)
86		return bld.exec_command(cmd2)
87
88########################## glib-mkenums
89
90@taskgen_method
91def add_enums_from_template(self, source='', target='', template='', comments=''):
92	"""
93	Adds a file to the list of enum files to process. Stores them in the attribute *enums_list*.
94
95	:param source: enum file to process
96	:type source: string
97	:param target: target file
98	:type target: string
99	:param template: template file
100	:type template: string
101	:param comments: comments
102	:type comments: string
103	"""
104	if not hasattr(self, 'enums_list'):
105		self.enums_list = []
106	self.meths.append('process_enums')
107	self.enums_list.append({'source': source,
108	                        'target': target,
109	                        'template': template,
110	                        'file-head': '',
111	                        'file-prod': '',
112	                        'file-tail': '',
113	                        'enum-prod': '',
114	                        'value-head': '',
115	                        'value-prod': '',
116	                        'value-tail': '',
117	                        'comments': comments})
118
119@taskgen_method
120def add_enums(self, source='', target='',
121              file_head='', file_prod='', file_tail='', enum_prod='',
122              value_head='', value_prod='', value_tail='', comments=''):
123	"""
124	Adds a file to the list of enum files to process. Stores them in the attribute *enums_list*.
125
126	:param source: enum file to process
127	:type source: string
128	:param target: target file
129	:type target: string
130	:param file_head: unused
131	:param file_prod: unused
132	:param file_tail: unused
133	:param enum_prod: unused
134	:param value_head: unused
135	:param value_prod: unused
136	:param value_tail: unused
137	:param comments: comments
138	:type comments: string
139	"""
140	if not hasattr(self, 'enums_list'):
141		self.enums_list = []
142	self.meths.append('process_enums')
143	self.enums_list.append({'source': source,
144	                        'template': '',
145	                        'target': target,
146	                        'file-head': file_head,
147	                        'file-prod': file_prod,
148	                        'file-tail': file_tail,
149	                        'enum-prod': enum_prod,
150	                        'value-head': value_head,
151	                        'value-prod': value_prod,
152	                        'value-tail': value_tail,
153	                        'comments': comments})
154
155@before_method('process_source')
156def process_enums(self):
157	"""
158	Processes the enum files stored in the attribute *enum_list* to create :py:class:`waflib.Tools.glib2.glib_mkenums` instances.
159	"""
160	for enum in getattr(self, 'enums_list', []):
161		task = self.create_task('glib_mkenums')
162		env = task.env
163
164		inputs = []
165
166		# process the source
167		source_list = self.to_list(enum['source'])
168		if not source_list:
169			raise Errors.WafError('missing source ' + str(enum))
170		source_list = [self.path.find_resource(k) for k in source_list]
171		inputs += source_list
172		env.GLIB_MKENUMS_SOURCE = [k.abspath() for k in source_list]
173
174		# find the target
175		if not enum['target']:
176			raise Errors.WafError('missing target ' + str(enum))
177		tgt_node = self.path.find_or_declare(enum['target'])
178		if tgt_node.name.endswith('.c'):
179			self.source.append(tgt_node)
180		env.GLIB_MKENUMS_TARGET = tgt_node.abspath()
181
182
183		options = []
184
185		if enum['template']: # template, if provided
186			template_node = self.path.find_resource(enum['template'])
187			options.append('--template %s' % (template_node.abspath()))
188			inputs.append(template_node)
189		params = {'file-head' : '--fhead',
190		           'file-prod' : '--fprod',
191		           'file-tail' : '--ftail',
192		           'enum-prod' : '--eprod',
193		           'value-head' : '--vhead',
194		           'value-prod' : '--vprod',
195		           'value-tail' : '--vtail',
196		           'comments': '--comments'}
197		for param, option in params.items():
198			if enum[param]:
199				options.append('%s %r' % (option, enum[param]))
200
201		env.GLIB_MKENUMS_OPTIONS = ' '.join(options)
202
203		# update the task instance
204		task.set_inputs(inputs)
205		task.set_outputs(tgt_node)
206
207class glib_mkenums(Task.Task):
208	"""
209	Processes enum files
210	"""
211	run_str = '${GLIB_MKENUMS} ${GLIB_MKENUMS_OPTIONS} ${GLIB_MKENUMS_SOURCE} > ${GLIB_MKENUMS_TARGET}'
212	color   = 'PINK'
213	ext_out = ['.h']
214
215######################################### gsettings
216
217@taskgen_method
218def add_settings_schemas(self, filename_list):
219	"""
220	Adds settings files to process to *settings_schema_files*
221
222	:param filename_list: files
223	:type filename_list: list of string
224	"""
225	if not hasattr(self, 'settings_schema_files'):
226		self.settings_schema_files = []
227
228	if not isinstance(filename_list, list):
229		filename_list = [filename_list]
230
231	self.settings_schema_files.extend(filename_list)
232
233@taskgen_method
234def add_settings_enums(self, namespace, filename_list):
235	"""
236	Called only once by task generator to set the enums namespace.
237
238	:param namespace: namespace
239	:type namespace: string
240	:param filename_list: enum files to process
241	:type filename_list: file list
242	"""
243	if hasattr(self, 'settings_enum_namespace'):
244		raise Errors.WafError("Tried to add gsettings enums to %r more than once" % self.name)
245	self.settings_enum_namespace = namespace
246
247	if not isinstance(filename_list, list):
248		filename_list = [filename_list]
249	self.settings_enum_files = filename_list
250
251@feature('glib2')
252def process_settings(self):
253	"""
254	Processes the schema files in *settings_schema_files* to create :py:class:`waflib.Tools.glib2.glib_mkenums` instances. The
255	same files are validated through :py:class:`waflib.Tools.glib2.glib_validate_schema` tasks.
256
257	"""
258	enums_tgt_node = []
259	install_files = []
260
261	settings_schema_files = getattr(self, 'settings_schema_files', [])
262	if settings_schema_files and not self.env.GLIB_COMPILE_SCHEMAS:
263		raise Errors.WafError ("Unable to process GSettings schemas - glib-compile-schemas was not found during configure")
264
265	# 1. process gsettings_enum_files (generate .enums.xml)
266	#
267	if hasattr(self, 'settings_enum_files'):
268		enums_task = self.create_task('glib_mkenums')
269
270		source_list = self.settings_enum_files
271		source_list = [self.path.find_resource(k) for k in source_list]
272		enums_task.set_inputs(source_list)
273		enums_task.env.GLIB_MKENUMS_SOURCE = [k.abspath() for k in source_list]
274
275		target = self.settings_enum_namespace + '.enums.xml'
276		tgt_node = self.path.find_or_declare(target)
277		enums_task.set_outputs(tgt_node)
278		enums_task.env.GLIB_MKENUMS_TARGET = tgt_node.abspath()
279		enums_tgt_node = [tgt_node]
280
281		install_files.append(tgt_node)
282
283		options = '--comments "<!-- @comment@ -->" --fhead "<schemalist>" --vhead "  <@type@ id=\\"%s.@EnumName@\\">" --vprod "    <value nick=\\"@valuenick@\\" value=\\"@valuenum@\\"/>" --vtail "  </@type@>" --ftail "</schemalist>" ' % (self.settings_enum_namespace)
284		enums_task.env.GLIB_MKENUMS_OPTIONS = options
285
286	# 2. process gsettings_schema_files (validate .gschema.xml files)
287	#
288	for schema in settings_schema_files:
289		schema_task = self.create_task ('glib_validate_schema')
290
291		schema_node = self.path.find_resource(schema)
292		if not schema_node:
293			raise Errors.WafError("Cannot find the schema file %r" % schema)
294		install_files.append(schema_node)
295		source_list = enums_tgt_node + [schema_node]
296
297		schema_task.set_inputs (source_list)
298		schema_task.env.GLIB_COMPILE_SCHEMAS_OPTIONS = [("--schema-file=" + k.abspath()) for k in source_list]
299
300		target_node = schema_node.change_ext('.xml.valid')
301		schema_task.set_outputs (target_node)
302		schema_task.env.GLIB_VALIDATE_SCHEMA_OUTPUT = target_node.abspath()
303
304	# 3. schemas install task
305	def compile_schemas_callback(bld):
306		if not bld.is_install:
307			return
308		compile_schemas = Utils.to_list(bld.env.GLIB_COMPILE_SCHEMAS)
309		destdir = Options.options.destdir
310		paths = bld._compile_schemas_registered
311		if destdir:
312			paths = (os.path.join(destdir, path.lstrip(os.sep)) for path in paths)
313		for path in paths:
314			Logs.pprint('YELLOW', 'Updating GSettings schema cache %r' % path)
315			if self.bld.exec_command(compile_schemas + [path]):
316				Logs.warn('Could not update GSettings schema cache %r' % path)
317
318	if self.bld.is_install:
319		schemadir = self.env.GSETTINGSSCHEMADIR
320		if not schemadir:
321			raise Errors.WafError ('GSETTINGSSCHEMADIR not defined (should have been set up automatically during configure)')
322
323		if install_files:
324			self.add_install_files(install_to=schemadir, install_from=install_files)
325			registered_schemas = getattr(self.bld, '_compile_schemas_registered', None)
326			if not registered_schemas:
327				registered_schemas = self.bld._compile_schemas_registered = set()
328				self.bld.add_post_fun(compile_schemas_callback)
329			registered_schemas.add(schemadir)
330
331class glib_validate_schema(Task.Task):
332	"""
333	Validates schema files
334	"""
335	run_str = 'rm -f ${GLIB_VALIDATE_SCHEMA_OUTPUT} && ${GLIB_COMPILE_SCHEMAS} --dry-run ${GLIB_COMPILE_SCHEMAS_OPTIONS} && touch ${GLIB_VALIDATE_SCHEMA_OUTPUT}'
336	color   = 'PINK'
337
338################## gresource
339
340@extension('.gresource.xml')
341def process_gresource_source(self, node):
342	"""
343	Creates tasks that turn ``.gresource.xml`` files to C code
344	"""
345	if not self.env.GLIB_COMPILE_RESOURCES:
346		raise Errors.WafError ("Unable to process GResource file - glib-compile-resources was not found during configure")
347
348	if 'gresource' in self.features:
349		return
350
351	h_node = node.change_ext('_xml.h')
352	c_node = node.change_ext('_xml.c')
353	self.create_task('glib_gresource_source', node, [h_node, c_node])
354	self.source.append(c_node)
355
356@feature('gresource')
357def process_gresource_bundle(self):
358	"""
359	Creates tasks to turn ``.gresource`` files from ``.gresource.xml`` files::
360
361		def build(bld):
362			bld(
363				features='gresource',
364				source=['resources1.gresource.xml', 'resources2.gresource.xml'],
365				install_path='${LIBDIR}/${PACKAGE}'
366			)
367
368	:param source: XML files to process
369	:type source: list of string
370	:param install_path: installation path
371	:type install_path: string
372	"""
373	for i in self.to_list(self.source):
374		node = self.path.find_resource(i)
375
376		task = self.create_task('glib_gresource_bundle', node, node.change_ext(''))
377		inst_to = getattr(self, 'install_path', None)
378		if inst_to:
379			self.add_install_files(install_to=inst_to, install_from=task.outputs)
380
381class glib_gresource_base(Task.Task):
382	"""
383	Base class for gresource based tasks
384	"""
385	color    = 'BLUE'
386	base_cmd = '${GLIB_COMPILE_RESOURCES} --sourcedir=${SRC[0].parent.srcpath()} --sourcedir=${SRC[0].bld_dir()}'
387
388	def scan(self):
389		"""
390		Scans gresource dependencies through ``glib-compile-resources --generate-dependencies command``
391		"""
392		bld = self.generator.bld
393		kw = {}
394		kw['cwd'] = self.get_cwd()
395		kw['quiet'] = Context.BOTH
396
397		cmd = Utils.subst_vars('${GLIB_COMPILE_RESOURCES} --sourcedir=%s --sourcedir=%s --generate-dependencies %s' % (
398			self.inputs[0].parent.srcpath(),
399			self.inputs[0].bld_dir(),
400			self.inputs[0].bldpath()
401		), self.env)
402
403		output = bld.cmd_and_log(cmd, **kw)
404
405		nodes = []
406		names = []
407		for dep in output.splitlines():
408			if dep:
409				node = bld.bldnode.find_node(dep)
410				if node:
411					nodes.append(node)
412				else:
413					names.append(dep)
414
415		return (nodes, names)
416
417class glib_gresource_source(glib_gresource_base):
418	"""
419	Task to generate C source code (.h and .c files) from a gresource.xml file
420	"""
421	vars    = ['GLIB_COMPILE_RESOURCES']
422	fun_h   = Task.compile_fun_shell(glib_gresource_base.base_cmd + ' --target=${TGT[0].abspath()} --generate-header ${SRC}')
423	fun_c   = Task.compile_fun_shell(glib_gresource_base.base_cmd + ' --target=${TGT[1].abspath()} --generate-source ${SRC}')
424	ext_out = ['.h']
425
426	def run(self):
427		return self.fun_h[0](self) or self.fun_c[0](self)
428
429class glib_gresource_bundle(glib_gresource_base):
430	"""
431	Task to generate a .gresource binary file from a gresource.xml file
432	"""
433	run_str = glib_gresource_base.base_cmd + ' --target=${TGT} ${SRC}'
434	shell   = True # temporary workaround for #795
435
436@conf
437def find_glib_genmarshal(conf):
438	conf.find_program('glib-genmarshal', var='GLIB_GENMARSHAL')
439
440@conf
441def find_glib_mkenums(conf):
442	if not conf.env.PERL:
443		conf.find_program('perl', var='PERL')
444	conf.find_program('glib-mkenums', interpreter='PERL', var='GLIB_MKENUMS')
445
446@conf
447def find_glib_compile_schemas(conf):
448	# when cross-compiling, gsettings.m4 locates the program with the following:
449	#   pkg-config --variable glib_compile_schemas gio-2.0
450	conf.find_program('glib-compile-schemas', var='GLIB_COMPILE_SCHEMAS')
451
452	def getstr(varname):
453		return getattr(Options.options, varname, getattr(conf.env,varname, ''))
454
455	gsettingsschemadir = getstr('GSETTINGSSCHEMADIR')
456	if not gsettingsschemadir:
457		datadir = getstr('DATADIR')
458		if not datadir:
459			prefix = conf.env.PREFIX
460			datadir = os.path.join(prefix, 'share')
461		gsettingsschemadir = os.path.join(datadir, 'glib-2.0', 'schemas')
462
463	conf.env.GSETTINGSSCHEMADIR = gsettingsschemadir
464
465@conf
466def find_glib_compile_resources(conf):
467	conf.find_program('glib-compile-resources', var='GLIB_COMPILE_RESOURCES')
468
469def configure(conf):
470	"""
471	Finds the following programs:
472
473	* *glib-genmarshal* and set *GLIB_GENMARSHAL*
474	* *glib-mkenums* and set *GLIB_MKENUMS*
475	* *glib-compile-schemas* and set *GLIB_COMPILE_SCHEMAS* (not mandatory)
476	* *glib-compile-resources* and set *GLIB_COMPILE_RESOURCES* (not mandatory)
477	"""
478	conf.find_glib_genmarshal()
479	conf.find_glib_mkenums()
480	conf.find_glib_compile_schemas(mandatory=False)
481	conf.find_glib_compile_resources(mandatory=False)
482
483def options(opt):
484	"""
485	Adds the ``--gsettingsschemadir`` command-line option
486	"""
487	gr = opt.add_option_group('Installation directories')
488	gr.add_option('--gsettingsschemadir', help='GSettings schema location [DATADIR/glib-2.0/schemas]', default='', dest='GSETTINGSSCHEMADIR')
489
490