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, sys
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		def char_to_num(ch):
99			if sys.version_info[0] < 3:
100				return ord(ch)
101			return ch
102
103		data = self.inputs[0].read('rb')
104		lines, line = [], []
105		for idx_byte, byte in enumerate(data):
106			line.append(byte)
107			if len(line) > 15 or idx_byte == size-1:
108				lines.append(", ".join(("0x%02x" % char_to_num(x)) for x in line))
109				line = []
110		data = ",\n ".join(lines)
111
112		self.outputs[0].write(\
113"""
114unsigned long %(name)s_size = %(size)dL;
115char const %(name)s_start[] = {
116 %(data)s
117};
118char const %(name)s_end[] = {};
119""" % locals())
120
121@TaskGen.feature('file_to_object')
122@TaskGen.before_method('process_source')
123def tg_file_to_object(self):
124	bld = self.bld
125	sources = self.to_nodes(self.source)
126	targets = []
127	for src in sources:
128		if bld.env.F2O_METHOD == ["asm"]:
129			tgt = src.parent.find_or_declare(src.name + '.f2o.s')
130			tsk = self.create_task('file_to_object_s', src, tgt)
131			tsk.cwd = src.parent.abspath() # verify
132		else:
133			tgt = src.parent.find_or_declare(src.name + '.f2o.c')
134			tsk = self.create_task('file_to_object_c', src, tgt)
135			tsk.cwd = src.parent.abspath() # verify
136		targets.append(tgt)
137	self.source = targets
138
139def configure(conf):
140	conf.load('gas')
141	conf.env.F2O_METHOD = ["c"]
142
143