1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
#
# Copyright 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
#
""" Automatically create YAML bindings for GRC from block code """
from __future__ import print_function
from __future__ import absolute_import
from __future__ import unicode_literals
import os
import re
import glob
import logging
import yaml
from collections import OrderedDict
try:
from yaml import CLoader as Loader, CDumper as Dumper
except:
from yaml import Loader, Dumper
try:
from gnuradio.blocktool.core import Constants
except ImportError:
have_blocktool = False
else:
have_blocktool = True
from ..tools import ParserCCBlock, CMakeFileEditor, ask_yes_no, GRCYAMLGenerator
from .base import ModTool, ModToolException
logger = logging.getLogger(__name__)
## setup dumper for dumping OrderedDict ##
_MAPPING_TAG = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
def dict_representer(dumper, data):
""" Representer to represent special OrderedDict """
return dumper.represent_dict(data.items())
def dict_constructor(loader, node):
""" Construct an OrderedDict for dumping """
return OrderedDict(loader.construct_pairs(node))
Dumper.add_representer(OrderedDict, dict_representer)
Loader.add_constructor(_MAPPING_TAG, dict_constructor)
class ModToolMakeYAML(ModTool):
""" Make YAML file for GRC block bindings """
name = 'makeyaml'
description = 'Generate YAML files for GRC block bindings.'
def __init__(self, blockname=None, **kwargs):
ModTool.__init__(self, blockname, **kwargs)
self.info['pattern'] = blockname
def validate(self):
""" Validates the arguments """
ModTool._validate(self)
if not self.info['pattern'] or self.info['pattern'].isspace():
raise ModToolException("Incorrect blockname (Regex)!")
def run(self):
""" Go, go, go! """
# This portion will be covered by the CLI
if not self.cli:
self.validate()
logger.warning("Warning: This is an experimental feature. Don't expect any magic.")
# 1) Go through lib/
if not self.skip_subdirs['lib']:
if self.info['version'] in ('37', '38'):
files = self._search_files('lib', '*_impl.cc')
else:
files = self._search_files('lib', '*.cc')
for f in files:
if os.path.basename(f)[0:2] == 'qa':
continue
(params, iosig, blockname) = self._parse_cc_h(f)
self._make_grc_yaml_from_block_data(params, iosig, blockname)
# 2) Go through python/
# TODO
def _search_files(self, path, path_glob):
""" Search for files matching pattern in the given path. """
files = sorted(glob.glob(f"{path}/{path_glob}"))
files_filt = []
logger.info(f"Searching for matching files in {path}/:")
for f in files:
if re.search(self.info['pattern'], os.path.basename(f)) is not None:
files_filt.append(f)
if len(files_filt) == 0:
logger.info("None found.")
return files_filt
def _make_grc_yaml_from_block_data(self, params, iosig, blockname):
""" Take the return values from the parser and call the YAML
generator. Also, check the makefile if the .yml file is in there.
If necessary, add. """
fname_yml = f'{self.info["modname"]}_{blockname}.block.yml'
path_to_yml = os.path.join('grc', fname_yml)
# Some adaptions for the GRC
for inout in ('in', 'out'):
if iosig[inout]['max_ports'] == '-1':
iosig[inout]['max_ports'] = f'$num_{inout}puts'
params.append({'key': f'num_{inout}puts',
'type': 'int',
'name': f'Num {inout}puts',
'default': '2',
'in_constructor': False})
file_exists = False
if os.path.isfile(path_to_yml):
if not self.info['yes']:
if not ask_yes_no('Overwrite existing GRC file?', False):
return
else:
file_exists = True
logger.warning("Warning: Overwriting existing GRC file.")
grc_generator = GRCYAMLGenerator(
modname=self.info['modname'],
blockname=blockname,
params=params,
iosig=iosig
)
grc_generator.save(path_to_yml)
if file_exists:
self.scm.mark_files_updated((path_to_yml,))
else:
self.scm.add_files((path_to_yml,))
if not self.skip_subdirs['grc']:
ed = CMakeFileEditor(self._file['cmgrc'])
if re.search(fname_yml, ed.cfile) is None and not ed.check_for_glob('*.yml'):
logger.info("Adding GRC bindings to grc/CMakeLists.txt...")
ed.append_value('install', fname_yml, to_ignore_end='DESTINATION[^()]+')
ed.write()
self.scm.mark_files_updated(self._file['cmgrc'])
def _parse_cc_h(self, fname_cc):
""" Go through a .cc and .h-file defining a block and return info """
def _type_translate(p_type, default_v=None):
""" Translates a type from C++ to GRC """
translate_dict = {'float': 'float',
'double': 'real',
'int': 'int',
'gr_complex': 'complex',
'char': 'byte',
'unsigned char': 'byte',
'std::string': 'string',
'std::vector<int>': 'int_vector',
'std::vector<float>': 'real_vector',
'std::vector<gr_complex>': 'complex_vector',
}
if p_type in ('int',) and default_v is not None and len(default_v) > 1 and default_v[:2].lower() == '0x':
return 'hex'
try:
return translate_dict[p_type]
except KeyError:
return 'raw'
def _get_blockdata(fname_cc):
""" Return the block name and the header file name from the .cc file name """
blockname = os.path.splitext(os.path.basename(fname_cc.replace('_impl.', '.')))[0]
fname_h = (blockname + '.h').replace('_impl.', '.')
contains_modulename = blockname.startswith(self.info['modname']+'_')
blockname = blockname.replace(self.info['modname']+'_', '', 1)
return (blockname, fname_h, contains_modulename)
# Go, go, go
logger.info(f"Making GRC bindings for {fname_cc}...")
(blockname, fname_h, contains_modulename) = _get_blockdata(fname_cc)
try:
parser = ParserCCBlock(fname_cc,
os.path.join(self.info['includedir'], fname_h),
blockname,
self.info['version'],
_type_translate
)
except IOError:
raise ModToolException(f"Can't open some of the files necessary to parse {fname_cc}.")
if contains_modulename:
return (parser.read_params(), parser.read_io_signature(), self.info['modname']+'_'+blockname)
else:
return (parser.read_params(), parser.read_io_signature(), blockname)
def yaml_generator(self, **kwargs):
"""
Generate YAML file from the block header file using blocktool API
"""
header = self.filename.split('.')[0]
block = self.modname.split('-')[-1]
label = header.split('_')
del label[-1]
yml_file = os.path.join('.', block+'_'+header+'.block.yml')
_header = (('id', f'{block}_{ header}'),
('label', ' '.join(label).upper()),
('category', f'[{block.capitalize()}]'),
('flags', '[python, cpp]')
)
params_list = [
'${'+s['name']+'}' for s in self.parsed_data['properties'] if self.parsed_data['properties']]
str_ = ', '.join(params_list)
_templates = [('imports', f'from gnuradio import {block}'),
('make', f'{block}.{ header}({str_})')
]
if self.parsed_data['methods']:
list_callbacks = []
for param in self.parsed_data['methods']:
arguments = []
for args in param['arguments_type']:
arguments.append(args['name'])
arg_list = ['${'+s+'}' for s in arguments if arguments]
arg_ = ', '.join(arg_list)
list_callbacks.append(
param['name']+f'({arg_})')
callback_key = ('callbacks')
callbacks = (callback_key, tuple(list_callbacks))
_templates.append(callbacks)
_templates = tuple(_templates)
data = OrderedDict()
for tag, value in _header:
data[tag] = value
templates = OrderedDict()
for tag, value in _templates:
templates[tag] = value
data['templates'] = templates
parameters = []
for param in self.parsed_data['properties']:
parameter = OrderedDict()
parameter['id'] = param['name']
parameter['label'] = param['name'].capitalize()
parameter['dtype'] = param['dtype']
parameter['read_only'] = param['read_only']
parameters.append(parameter)
if parameters:
data['parameters'] = parameters
input_signature = []
max_input_port = self.parsed_data['io_signature']['input']['max_streams']
i_sig = self.parsed_data['io_signature']['input']['signature']
for port in range(0, int(max_input_port)):
input_sig = OrderedDict()
if i_sig is Constants.MAKE:
input_sig['domain'] = 'stream'
input_sig['dtype'] = self.parsed_data['io_signature']['input']['sizeof_stream_item']
elif i_sig is Constants.MAKE2:
input_sig['domain'] = 'stream'
input_sig['dtype'] = self.parsed_data['io_signature']['input']['sizeof_stream_item' +
str(port+1)]
elif i_sig is Constants.MAKE3:
input_sig['domain'] = 'stream'
input_sig['dtype'] = self.parsed_data['io_signature']['input']['sizeof_stream_item' +
str(port+1)]
elif i_sig is Constants.MAKEV:
input_sig['domain'] = 'stream'
input_sig['dtype'] = self.parsed_data['io_signature']['input']['sizeof_stream_items']
input_signature.append(input_sig)
if self.parsed_data['message_port']['input']:
for _input in self.parsed_data['message_port']['input']:
m_input_sig = OrderedDict()
m_input_sig['domain'] = 'message'
m_input_sig['id'] = _input
input_signature.append(m_input_sig)
if input_signature:
data['inputs'] = input_signature
output_signature = []
max_output_port = self.parsed_data['io_signature']['output']['max_streams']
o_sig = self.parsed_data['io_signature']['output']['signature']
for port in range(0, int(max_output_port)):
output_sig = OrderedDict()
if o_sig is Constants.MAKE:
output_sig['domain'] = 'stream'
output_sig['dtype'] = self.parsed_data['io_signature']['output']['sizeof_stream_item']
elif o_sig is Constants.MAKE2:
output_sig['domain'] = 'stream'
output_sig['dtype'] = self.parsed_data['io_signature']['output']['sizeof_stream_item' +
str(port+1)]
elif o_sig is Constants.MAKE3:
output_sig['domain'] = 'stream'
output_sig['dtype'] = self.parsed_data['io_signature']['output']['sizeof_stream_item' +
str(port+1)]
elif o_sig is Constants.MAKEV:
output_sig['domain'] = 'stream'
output_sig['dtype'] = self.parsed_data['io_signature']['output']['sizeof_stream_items']
output_signature.append(output_sig)
if self.parsed_data['message_port']['output']:
for _output in self.parsed_data['message_port']['output']:
m_output_sig = OrderedDict()
m_output_sig['domain'] = 'message'
m_output_sig['id'] = _output
output_signature.append(m_output_sig)
if output_signature:
data['outputs'] = output_signature
param_ = ', '.join(params_list)
_cpp_templates = [('includes', '#include <gnuradio/{block}/{self.filename}>'),
('declarations', '{block}::{ header}::sptr ${{id}}'),
('make', 'this->${{id}} = {block}::{ header}::make({param_})')
]
if self.parsed_data['methods']:
list_callbacks = []
for param in self.parsed_data['methods']:
arguments = []
for args in param['arguments_type']:
arguments.append(args['name'])
arg_list = ['${'+s+'}' for s in arguments if arguments]
arg_ = ', '.join(arg_list)
list_callbacks.append(
param['name']+f'({arg_})')
callback_key = ('callbacks')
callbacks = (callback_key, tuple(list_callbacks))
_cpp_templates.append(callbacks)
link = ('link', 'gnuradio-{block}')
_cpp_templates.append(link)
_cpp_templates = tuple(_cpp_templates)
cpp_templates = OrderedDict()
for tag, value in _cpp_templates:
cpp_templates[tag] = value
data['cpp_templates'] = cpp_templates
if self.parsed_data['docstring'] is not None:
data['documentation'] = self.parsed_data['docstring']
data['file_format'] = 1
if kwargs['output']:
with open(yml_file, 'w') as yml:
yaml.dump(data, yml, Dumper=Dumper, default_flow_style=False)
else:
print(yaml.dump(data, Dumper=Dumper, allow_unicode=True,
default_flow_style=False, indent=4))
|