1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3# Tool to embed file into objects
4
5__author__ = __maintainer__ = "Jérôme Carretero <cJ-waf@zougloub.eu>"
6__copyright__ = "Jérôme Carretero, 2014"
7
8"""
9
10This tool allows to embed file contents in object files (.o).
11It is not exactly portable, and the file contents are reachable
12using various non-portable fashions.
13The goal here is to provide a functional interface to the embedding
14of file data in objects.
15See the ``playground/embedded_resources`` example for an example.
16
17Usage::
18
19   bld(
20    name='pipeline',
21     # ^ Reference this in use="..." for things using the generated code
22    features='file_to_object',
23    source='some.file',
24     # ^ Name of the file to embed in binary section.
25   )
26
27Known issues:
28
29- Destination is named like source, with extension renamed to .o
30  eg. some.file -> some.o
31
32"""
33
34import os
35from waflib import Task, TaskGen, Errors
36
37def filename_c_escape(x):
38	return x.replace("\\", "\\\\")
39
40class file_to_object_s(Task.Task):
41	color = 'CYAN'
42	vars = ['DEST_CPU', 'DEST_BINFMT']
43
44	def run(self):
45		name = []
46		for i, x in enumerate(self.inputs[0].name):
47			if x.isalnum():
48				name.append(x)
49			else:
50				name.append('_')
51		file = self.inputs[0].abspath()
52		size = os.path.getsize(file)
53		if self.env.DEST_CPU in ('x86_64', 'ia', 'aarch64'):
54			unit = 'quad'
55			align = 8
56		elif self.env.DEST_CPU in ('x86','arm', 'thumb', 'm68k'):
57			unit = 'long'
58			align = 4
59		else:
60			raise Errors.WafError("Unsupported DEST_CPU, please report bug!")
61
62		file = filename_c_escape(file)
63		name = "_binary_" + "".join(name)
64		rodata = ".section .rodata"
65		if self.env.DEST_BINFMT == "mac-o":
66			name = "_" + name
67			rodata = ".section __TEXT,__const"
68
69		with open(self.outputs[0].abspath(), 'w') as f:
70			f.write(\
71"""
72	.global %(name)s_start
73	.global %(name)s_end
74	.global %(name)s_size
75	%(rodata)s
76%(name)s_start:
77	.incbin "%(file)s"
78%(name)s_end:
79	.align %(align)d
80%(name)s_size:
81	.%(unit)s 0x%(size)x
82""" % locals())
83
84class file_to_object_c(Task.Task):
85	color = 'CYAN'
86	def run(self):
87		name = []
88		for i, x in enumerate(self.inputs[0].name):
89			if x.isalnum():
90				name.append(x)
91			else:
92				name.append('_')
93		file = self.inputs[0].abspath()
94		size = os.path.getsize(file)
95
96		name = "_binary_" + "".join(name)
97
98		data = self.inputs[0].read('rb')
99		lines, line = [], []
100		for idx_byte, byte in enumerate(data):
101			line.append(byte)
102			if len(line) > 15 or idx_byte == size-1:
103				lines.append(", ".join(("0x%02x" % ord(x)) for x in line))
104				line = []
105		data = ",\n ".join(lines)
106
107		self.outputs[0].write(\
108"""
109unsigned long %(name)s_size = %(size)dL;
110char const %(name)s_start[] = {
111 %(data)s
112};
113char const %(name)s_end[] = {};
114""" % locals())
115
116@TaskGen.feature('file_to_object')
117@TaskGen.before_method('process_source')
118def tg_file_to_object(self):
119	bld = self.bld
120	sources = self.to_nodes(self.source)
121	targets = []
122	for src in sources:
123		if bld.env.F2O_METHOD == ["asm"]:
124			tgt = src.parent.find_or_declare(src.name + '.f2o.s')
125			tsk = self.create_task('file_to_object_s', src, tgt)
126			tsk.cwd = src.parent.abspath() # verify
127		else:
128			tgt = src.parent.find_or_declare(src.name + '.f2o.c')
129			tsk = self.create_task('file_to_object_c', src, tgt)
130			tsk.cwd = src.parent.abspath() # verify
131		targets.append(tgt)
132	self.source = targets
133
134def configure(conf):
135	conf.load('gas')
136	conf.env.F2O_METHOD = ["c"]
137
138