summaryrefslogtreecommitdiff
path: root/grc
diff options
context:
space:
mode:
Diffstat (limited to 'grc')
-rw-r--r--grc/blocks/options.block.yml28
-rw-r--r--grc/blocks/stream.domain.yml3
-rw-r--r--grc/blocks/variable.block.yml6
-rw-r--r--grc/core/FlowGraph.py4
-rw-r--r--grc/core/blocks/_build.py14
-rw-r--r--grc/core/blocks/_flags.py2
-rw-r--r--grc/core/blocks/block.py98
-rw-r--r--grc/core/generator/FlowGraphProxy.py30
-rw-r--r--grc/core/generator/Generator.py26
-rw-r--r--grc/core/generator/cpp_hier_block.py196
-rw-r--r--grc/core/generator/cpp_templates/CMakeLists.txt.mako64
-rw-r--r--grc/core/generator/cpp_templates/flow_graph.cpp.mako155
-rw-r--r--grc/core/generator/cpp_templates/flow_graph.hpp.mako89
-rw-r--r--grc/core/generator/cpp_top_block.py344
-rw-r--r--grc/core/generator/top_block.py31
-rw-r--r--grc/core/platform.py17
-rw-r--r--grc/core/schema_checker/block.py9
-rw-r--r--grc/gui/Executor.py74
-rw-r--r--grc/tests/test_cpp.py18
19 files changed, 1189 insertions, 19 deletions
diff --git a/grc/blocks/options.block.yml b/grc/blocks/options.block.yml
index 269351ddc0..dec77a3f84 100644
--- a/grc/blocks/options.block.yml
+++ b/grc/blocks/options.block.yml
@@ -1,5 +1,6 @@
id: options
label: Options
+flags: has_python, has_cpp
parameters:
- id: title
@@ -22,12 +23,36 @@ parameters:
label: Canvas Size
dtype: int_vector
hide: part
+- id: output_language
+ label: Output Language
+ dtype: enum
+ default: python
+ options: [python, cpp]
+ option_labels: [Python, C++]
- id: generate_options
label: Generate Options
dtype: enum
default: qt_gui
options: [qt_gui, bokeh_gui, no_gui, hb, hb_qt_gui]
option_labels: [QT GUI, Bokeh GUI, No GUI, Hier Block, Hier Block (QT GUI)]
+- id: gen_linking
+ label: Linking
+ dtype: enum
+ default: dynamic
+ options: [dynamic, static]
+ option_labels: [ Dynamic, Static ]
+ hide: ${ ('part' if output_language == 'cpp' else 'all') }
+- id: gen_cmake
+ label: Generate CMakeLists.txt
+ dtype: enum
+ default: 'On'
+ options: ['On', 'Off']
+ hide: ${ ('part' if output_language == 'cpp' else 'all') }
+- id: cmake_opt
+ label: CMake options
+ dtype: string
+ default: ''
+ hide: ${ ('part' if output_language == 'cpp' else 'all') }
- id: category
label: Category
dtype: string
@@ -130,6 +155,9 @@ templates:
else: self.stop(); self.wait()'
+cpp_templates:
+ includes: '#include <gnuradio/top_block.h>'
+
documentation: |-
The options block sets special parameters for the flow graph. Only one option block is allowed per flow graph.
diff --git a/grc/blocks/stream.domain.yml b/grc/blocks/stream.domain.yml
index a4d786f8b4..f97695b828 100644
--- a/grc/blocks/stream.domain.yml
+++ b/grc/blocks/stream.domain.yml
@@ -7,4 +7,5 @@ multiple_connections_per_output: true
templates:
- type: [stream, stream]
- connect: self.connect(${ make_port_sig(source) }, ${ make_port_sig(sink) })
+ connect: connect(${ make_port_sig(source) }, ${ make_port_sig(sink) })
+ cpp_connect: hier_block2::connect(${ make_port_sig(source) }, ${ make_port_sig(sink) })
diff --git a/grc/blocks/variable.block.yml b/grc/blocks/variable.block.yml
index fa62dabe87..045511afb0 100644
--- a/grc/blocks/variable.block.yml
+++ b/grc/blocks/variable.block.yml
@@ -1,5 +1,6 @@
id: variable
label: Variable
+flags: has_python, has_cpp
parameters:
- id: value
@@ -13,6 +14,11 @@ templates:
callbacks:
- self.set_${id}(${value})
+cpp_templates:
+ var_make: ${id} = ${value};
+ callbacks:
+ - this->set_${id}(${value})
+
documentation: |-
This block maps a value to a unique variable. This variable block has no graphical representation.
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index a47513d314..6786bbee30 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -64,7 +64,7 @@ class FlowGraph(Element):
def imports(self):
"""
- Get a set of all import statements in this flow graph namespace.
+ Get a set of all import statements (Python) in this flow graph namespace.
Returns:
a list of import statements
@@ -73,7 +73,7 @@ class FlowGraph(Element):
def get_variables(self):
"""
- Get a list of all variables in this flow graph namespace.
+ Get a list of all variables (Python) in this flow graph namespace.
Exclude parameterized variables.
Returns:
diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py
index 0c41d1ae70..bed0f6934a 100644
--- a/grc/core/blocks/_build.py
+++ b/grc/core/blocks/_build.py
@@ -33,7 +33,7 @@ from ._templates import MakoTemplates
def build(id, label='', category='', flags='', documentation='',
value=None, asserts=None,
- parameters=None, inputs=None, outputs=None, templates=None, **kwargs):
+ parameters=None, inputs=None, outputs=None, templates=None, cpp_templates=None, **kwargs):
block_id = id
cls = type(str(block_id), (Block,), {})
@@ -63,6 +63,17 @@ def build(id, label='', category='', flags='', documentation='',
callbacks=templates.get('callbacks', []),
var_make=templates.get('var_make', ''),
)
+
+ cpp_templates = cpp_templates or {}
+ cls.cpp_templates = MakoTemplates(
+ includes=cpp_templates.get('includes', ''),
+ make=cpp_templates.get('make', ''),
+ callbacks=cpp_templates.get('callbacks', []),
+ var_make=cpp_templates.get('var_make', ''),
+ link=cpp_templates.get('link', []),
+ translations=cpp_templates.get('translations', []),
+ declarations=cpp_templates.get('declarations', ''),
+ )
# todo: MakoTemplates.compile() to check for errors
cls.value = _single_mako_expr(value, block_id)
@@ -149,4 +160,3 @@ def _validate_option_attributes(param_data, block_id):
if key in dir(str):
del param_data['option_attributes'][key]
send_warning('{} - option_attribute "{}" overrides str, ignoring'.format(block_id, key))
-
diff --git a/grc/core/blocks/_flags.py b/grc/core/blocks/_flags.py
index bbedd6a2d7..ec590ebbb5 100644
--- a/grc/core/blocks/_flags.py
+++ b/grc/core/blocks/_flags.py
@@ -25,6 +25,8 @@ class Flags(object):
NEED_QT_GUI = 'need_qt_gui'
DEPRECATED = 'deprecated'
NOT_DSP = 'not_dsp'
+ HAS_PYTHON = 'has_python'
+ HAS_CPP = 'has_cpp'
def __init__(self, flags):
self.data = set(flags)
diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py
index c3502397ec..4df3ec9663 100644
--- a/grc/core/blocks/block.py
+++ b/grc/core/blocks/block.py
@@ -21,9 +21,11 @@ from __future__ import absolute_import
import collections
import itertools
+import copy
import six
from six.moves import range
+import re
from ._templates import MakoTemplates
from ._flags import Flags
@@ -31,7 +33,6 @@ from ._flags import Flags
from ..base import Element
from ..utils.descriptors import lazy_property
-
def _get_elem(iterable, key):
items = list(iterable)
for item in items:
@@ -49,6 +50,7 @@ class Block(Element):
key = ''
label = ''
category = ''
+ vtype = '' # This is only used for variables when we want C++ output
flags = Flags('')
documentation = {'': ''}
@@ -83,6 +85,7 @@ class Block(Element):
self.active_sinks = [] # on rewrite
self.states = {'state': True}
+ self.orig_cpp_templates = self.cpp_templates # The original template, in case we have to edit it when transpiling to C++
# region Rewrite_and_Validation
def rewrite(self):
@@ -136,6 +139,7 @@ class Block(Element):
Element.validate(self)
self._run_asserts()
self._validate_generate_mode_compat()
+ self._validate_output_language_compat()
self._validate_var_value()
def _run_asserts(self):
@@ -161,6 +165,13 @@ class Block(Element):
check_generate_mode('QT GUI', Flags.NEED_QT_GUI, ('qt_gui', 'hb_qt_gui'))
+ def _validate_output_language_compat(self):
+ """check if this block supports the selected output language"""
+ current_output_language = self.parent.get_option('output_language')
+
+ if current_output_language == 'cpp' and 'has_cpp' not in self.flags:
+ self.add_error_message("This block does not support C++ output.")
+
def _validate_var_value(self):
"""or variables check the value (only if var_value is used)"""
if self.is_variable and self.value != 'value':
@@ -227,6 +238,9 @@ class Block(Element):
def get_var_make(self):
return self.templates.render('var_make')
+ def get_cpp_var_make(self):
+ return self.cpp_templates.render('var_make')
+
def get_var_value(self):
return self.templates.render('var_value')
@@ -241,8 +255,88 @@ class Block(Element):
if 'self.' in callback:
return callback
return 'self.{}.{}'.format(self.name, callback)
+
return [make_callback(c) for c in self.templates.render('callbacks')]
+ def get_cpp_callbacks(self):
+ """
+ Get a list of C++ function callbacks for this block.
+
+ Returns:
+ a list of strings
+ """
+ def make_callback(callback):
+ if 'this->' in callback:
+ return callback
+ return 'this->{}->{}'.format(self.name, callback)
+
+ return [make_callback(c) for c in self.cpp_templates.render('callbacks')]
+
+ def decide_type(self):
+ """
+ Evaluate the value of the variable block and decide its type.
+
+ Returns:
+ None
+ """
+ value = self.params['value'].value
+ self.cpp_templates = copy.copy(self.orig_cpp_templates)
+
+ def get_type(element):
+ try:
+ evaluated = ast.literal_eval(element)
+
+ except ValueError or SyntaxError:
+ if re.match(r'^(numpy|np|scipy|sp)\.pi$', value):
+ return 'pi'
+ else:
+ return 'std::string'
+
+ else:
+ _vtype = type(evaluated)
+ if _vtype in [int, float, bool, list]:
+ if _vtype == (int or long):
+ return 'int'
+
+ if _vtype == float:
+ return 'double'
+
+ if _vtype == bool:
+ return 'bool'
+
+ if _vtype == list:
+ try:
+ first_element_type = type(evaluated[0])
+ if first_element_type != str:
+ list_type = get_type(str(evaluated[0]))
+ else:
+ list_type = get_type(evaluated[0])
+
+ except IndexError: # empty list
+ return 'std::vector<std::string>'
+
+ else:
+ return 'std::vector<' + list_type + '>'
+
+ else:
+ return 'std::string'
+
+ self.vtype = get_type(value)
+ if self.vtype == 'bool':
+ self.cpp_templates['var_make'] = self.cpp_templates['var_make'].replace('${value}', (value[0].lower() + value[1:]))
+
+ elif self.vtype == 'pi':
+ self.vtype = 'double'
+ self.cpp_templates['var_make'] = self.cpp_templates['var_make'].replace('${value}', 'boost::math::constants::pi<double>()')
+ self.cpp_templates['includes'].append('#include <boost/math/constants/constants.hpp>')
+
+ elif 'std::vector' in self.vtype:
+ self.cpp_templates['includes'].append('#include <vector>')
+ self.cpp_templates['var_make'] = self.cpp_templates['var_make'].replace('${value}', '{' + value[1:-1] + '}')
+
+ if 'string' in self.vtype:
+ self.cpp_templates['includes'].append('#include <string>')
+
def is_virtual_sink(self):
return self.key == 'virtual_sink'
@@ -261,7 +355,7 @@ class Block(Element):
Bypass the block
Returns:
- True if block chagnes state
+ True if block changes state
"""
if self.state != 'bypassed' and self.can_bypass():
self.state = 'bypassed'
diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py
index f438fa0d39..8068156c0d 100644
--- a/grc/core/generator/FlowGraphProxy.py
+++ b/grc/core/generator/FlowGraphProxy.py
@@ -19,6 +19,8 @@
from __future__ import absolute_import
from six.moves import range
+from ..utils import expr_utils
+from operator import methodcaller, attrgetter
class FlowGraphProxy(object): # TODO: move this in a refactored Generator
@@ -130,6 +132,34 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator
key_offset += len(pad.sinks) + len(pad.sources)
return -1
+ def get_cpp_variables(self):
+ """
+ Get a list of all variables (C++) in this flow graph namespace.
+ Exclude parameterized variables.
+
+ Returns:
+ a sorted list of variable blocks in order of dependency (indep -> dep)
+ """
+ variables = [block for block in self.iter_enabled_blocks() if block.is_variable]
+ return expr_utils.sort_objects(variables, attrgetter('name'), methodcaller('get_cpp_var_make'))
+
+ def includes(self):
+ """
+ Get a set of all include statements (C++) in this flow graph namespace.
+
+ Returns:
+ a list of #include statements
+ """
+ return [block.cpp_templates.render('includes') for block in self.iter_enabled_blocks()]
+
+ def links(self):
+ """
+ Get a set of all libraries to link against (C++) in this flow graph namespace.
+
+ Returns:
+ a list of GNU Radio modules
+ """
+ return [block.cpp_templates.render('link') for block in self.iter_enabled_blocks()]
def get_hier_block_io(flow_graph, direction, domain=None):
"""
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index 62dc26b8a8..91526d8714 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -24,6 +24,7 @@ from mako.template import Template
from .hier_block import HierBlockGenerator, QtHierBlockGenerator
from .top_block import TopBlockGenerator
+from .cpp_top_block import CppTopBlockGenerator
DATA_DIR = os.path.dirname(__file__)
FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.py.mako')
@@ -43,12 +44,25 @@ class Generator(object):
file_path: the path to the grc file
"""
self.generate_options = flow_graph.get_option('generate_options')
- if self.generate_options == 'hb':
- generator_cls = HierBlockGenerator
- elif self.generate_options == 'hb_qt_gui':
- generator_cls = QtHierBlockGenerator
- else:
- generator_cls = TopBlockGenerator
+ self.output_language = flow_graph.get_option('output_language')
+
+ if self.output_language == 'python':
+
+ if self.generate_options == 'hb':
+ generator_cls = HierBlockGenerator
+ elif self.generate_options == 'hb_qt_gui':
+ generator_cls = QtHierBlockGenerator
+ else:
+ generator_cls = TopBlockGenerator
+
+ elif self.output_language == 'cpp':
+
+ if self.generate_options == 'hb':
+ pass
+ elif self.generate_options == 'hb_qt_gui':
+ pass
+ else:
+ generator_cls = CppTopBlockGenerator
self._generator = generator_cls(flow_graph, file_path)
diff --git a/grc/core/generator/cpp_hier_block.py b/grc/core/generator/cpp_hier_block.py
new file mode 100644
index 0000000000..e81d62c549
--- /dev/null
+++ b/grc/core/generator/cpp_hier_block.py
@@ -0,0 +1,196 @@
+import collections
+import os
+
+import six
+
+from .top_block import TopBlockGenerator
+
+from .. import ParseXML, Constants
+
+class CppHierBlockGenerator(CppTopBlockGenerator):
+ """Extends the top block generator to also generate a block XML file"""
+
+ def __init__(self, flow_graph, file_path):
+ """
+ Initialize the hier block generator object.
+
+ Args:
+ flow_graph: the flow graph object
+ file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR)
+ """
+ CppTopBlockGenerator.__init__(self, flow_graph, file_path)
+ platform = flow_graph.parent
+
+ hier_block_lib_dir = platform.config.hier_block_lib_dir
+ if not os.path.exists(hier_block_lib_dir):
+ os.mkdir(hier_block_lib_dir)
+
+ self._mode = Constants.HIER_BLOCK_FILE_MODE
+ self.file_path = os.path.join(hier_block_lib_dir, self._flow_graph.get_option('id'))
+ self.file_path_xml = self.file_path + '.xml' # TODO: yml
+
+ def write(self):
+ """generate output and write it to files"""
+ TopBlockGenerator.write(self)
+ ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.file_path_xml)
+ ParseXML.validate_dtd(self.file_path_xml, Constants.BLOCK_DTD)
+ try:
+ os.chmod(self.file_path_xml, self._mode)
+ except:
+ pass
+
+ def _build_block_n_from_flow_graph_io(self):
+ """
+ Generate a block XML nested data from the flow graph IO
+
+ Returns:
+ a xml node tree
+ """
+ # Extract info from the flow graph
+ block_id = self._flow_graph.get_option('id')
+ parameters = self._flow_graph.get_parameters()
+
+ def var_or_value(name):
+ if name in (p.name for p in parameters):
+ return "${" + name + " }"
+ return name
+
+ # Build the nested data
+ data = collections.OrderedDict()
+ data['id'] = block_id
+ data['label'] = (
+ self._flow_graph.get_option('title') or
+ self._flow_graph.get_option('id').replace('_', ' ').title()
+ )
+ data['category'] = self._flow_graph.get_option('category')
+
+ # Parameters
+ data['parameters'] = []
+ for param_block in parameters:
+ p = collections.OrderedDict()
+ p['id'] = param_block.name
+ p['label'] = param_block.params['label'].get_value() or param_block.name
+ p['dtype'] = 'raw'
+ p['value'] = param_block.params['value'].get_value()
+ p['hide'] = param_block.params['hide'].get_value()
+ data['param'].append(p)
+
+ # Ports
+ for direction in ('inputs', 'outputs'):
+ data[direction] = []
+ for port in get_hier_block_io(self._flow_graph, direction):
+ p = collections.OrderedDict()
+ if port.domain == Constants.GR_MESSAGE_DOMAIN:
+ p['id'] = port.id
+ #p['label'] = port.label
+ if port.domain != Constants.DEFAULT_DOMAIN:
+ p['domain'] = port.domain
+ p['dtype'] = port.dtype
+ if port.domain != Constants.GR_MESSAGE_DOMAIN:
+ p['vlen'] = var_or_value(port.vlen)
+ if port.optional:
+ p['optional'] = True
+ data[direction].append(p)
+
+ t = data['templates'] = collections.OrderedDict()
+
+ t['import'] = "from {0} import {0} # grc-generated hier_block".format(
+ self._flow_graph.get_option('id'))
+ # Make data
+ if parameters:
+ t['make'] = '{cls}(\n {kwargs},\n)'.format(
+ cls=block_id,
+ kwargs=',\n '.join(
+ '{key}=${key}'.format(key=param.name) for param in parameters
+ ),
+ )
+ else:
+ t['make'] = '{cls}()'.format(cls=block_id)
+ # Callback data
+ t['callback'] = [
+ 'set_{key}(${key})'.format(key=param_block.name) for param_block in parameters
+ ]
+
+
+ # Documentation
+ data['doc'] = "\n".join(field for field in (
+ self._flow_graph.get_option('author'),
+ self._flow_graph.get_option('description'),
+ self.file_path
+ ) if field)
+ data['grc_source'] = str(self._flow_graph.grc_file_path)
+
+ n = {'block': data}
+ return n
+
+
+class CppQtHierBlockGenerator(CppHierBlockGenerator):
+
+ def _build_block_n_from_flow_graph_io(self):
+ n = CppHierBlockGenerator._build_block_n_from_flow_graph_io(self)
+ block_n = collections.OrderedDict()
+
+ # insert flags after category
+ for key, value in six.iteritems(n['block']):
+ block_n[key] = value
+ if key == 'category':
+ block_n['flags'] = 'need_qt_gui'
+
+ if not block_n['name'].upper().startswith('QT GUI'):
+ block_n['name'] = 'QT GUI ' + block_n['name']
+
+ gui_hint_param = collections.OrderedDict()
+ gui_hint_param['name'] = 'GUI Hint'
+ gui_hint_param['key'] = 'gui_hint'
+ gui_hint_param['value'] = ''
+ gui_hint_param['type'] = 'gui_hint'
+ gui_hint_param['hide'] = 'part'
+ block_n['param'].append(gui_hint_param)
+
+ block_n['make'] += (
+ "\n#set $win = 'self.%s' % $id"
+ "\n${gui_hint()($win)}"
+ )
+
+ return {'block': block_n}
+
+
+def get_hier_block_io(flow_graph, direction, domain=None):
+ """
+ Get a list of io ports for this flow graph.
+
+ Returns a list of dicts with: type, label, vlen, size, optional
+ """
+ pads = flow_graph.get_pad_sources() if direction == 'inputs' else flow_graph.get_pad_sinks()
+
+ ports = []
+ for pad in pads:
+ for port in (pad.sources if direction == 'inputs' else pad.sinks):
+ if domain and port.domain != domain:
+ continue
+ yield port
+
+ type_param = pad.params['type']
+ master = {
+ 'label': str(pad.params['label'].get_evaluated()),
+ 'type': str(pad.params['type'].get_evaluated()),
+ 'vlen': str(pad.params['vlen'].get_value()),
+ 'size': type_param.options.attributes[type_param.get_value()]['size'],
+ 'optional': bool(pad.params['optional'].get_evaluated()),
+ }
+
+ if domain and pad:
+ num_ports = pad.params['num_streams'].get_evaluated()
+ if num_ports <= 1:
+ yield master
+ else:
+ for i in range(num_ports):
+ clone = master.copy()
+ clone['label'] += str(i)
+ ports.append(clone)
+ else:
+ ports.append(master)
+
+ if domain is not None:
+ ports = [p for p in ports if p.domain == domain]
+ yield ports # TODO: Not sure this fix is correct
diff --git a/grc/core/generator/cpp_templates/CMakeLists.txt.mako b/grc/core/generator/cpp_templates/CMakeLists.txt.mako
new file mode 100644
index 0000000000..56f39c6b22
--- /dev/null
+++ b/grc/core/generator/cpp_templates/CMakeLists.txt.mako
@@ -0,0 +1,64 @@
+#####################
+# GNU Radio C++ Flow Graph CMakeLists.txt
+#
+# Title: ${title}
+# % if flow_graph.get_option('author'):
+# Author: ${flow_graph.get_option('author')}
+# % endif
+# % if flow_graph.get_option('description'):
+# Description: ${flow_graph.get_option('description')}
+# % endif
+# Generated: ${generated_time}
+#####################
+
+<%
+class_name = flow_graph.get_option('id')
+cmake_opt_list = flow_graph.get_option('cmake_opt').split(";")
+%>\
+
+# cmake_minimum_required(VERSION 3.8) Which version?
+
+% if generate_options == 'qt_gui':
+find_package(Qt5Widgets REQUIRED)
+% endif
+
+include_directories(
+ ${'$'}{GNURADIO_ALL_INCLUDE_DIRS}
+ ${'$'}{Boost_INCLUDE_DIRS}
+ % if generate_options == 'qt_gui':
+ ${'$'}{Qt5Widgets_INCLUDES}
+ % endif
+)
+
+% if generate_options == 'qt_gui':
+add_definitions(${'$'}{Qt5Widgets_DEFINITIONS})
+
+set(CMAKE_AUTOMOC TRUE)
+% endif
+
+% if cmake_opt_list != ['']:
+% for opt in cmake_opt_list:
+set(${opt.split("=")[0].strip()} ${opt.split("=")[1].strip()})
+% endfor
+% endif
+
+% if flow_graph.get_option('gen_linking') == 'static':
+set(BUILD_SHARED_LIBS false)
+set(CMAKE_EXE_LINKER_FLAGS " -static")
+set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
+% endif
+
+set(GR_LIBRARIES
+ boost_system
+ gnuradio-blocks
+ gnuradio-runtime
+ gnuradio-pmt
+ % for link in links:
+ % if link != '':
+ ${link}
+ % endif
+ % endfor
+)
+
+add_executable(${class_name} ${class_name}.cpp)
+target_link_libraries(${class_name} ${'$'}{GR_LIBRARIES})
diff --git a/grc/core/generator/cpp_templates/flow_graph.cpp.mako b/grc/core/generator/cpp_templates/flow_graph.cpp.mako
new file mode 100644
index 0000000000..b919015f86
--- /dev/null
+++ b/grc/core/generator/cpp_templates/flow_graph.cpp.mako
@@ -0,0 +1,155 @@
+/********************
+GNU Radio C++ Flow Graph Source File
+
+Title: ${title}
+% if flow_graph.get_option('author'):
+Author: ${flow_graph.get_option('author')}
+% endif
+% if flow_graph.get_option('description'):
+Description: ${flow_graph.get_option('description')}
+% endif
+Generated: ${generated_time}
+********************/
+
+#include "${flow_graph.get_option('id')}.hpp"
+
+using namespace gr;
+
+<%
+ class_name = flow_graph.get_option('id')
+## TODO: param_str
+%>\
+<%def name="indent(code)">${ '\n '.join(str(code).splitlines()) }</%def>
+
+## TODO: param_str
+% if generate_options == 'no_gui':
+${class_name}::${class_name} () : top_block("${title}") {
+% elif generate_options.startswith('hb'):
+## TODO: make_io_sig
+${class_name}::${class_name} () : hier_block2("${title}") {
+% for pad in flow_graph.get_hier_block_message_io('in'):
+ message_port_register_hier_in("${pad['label']}")
+% endfor
+% for pad in flow_graph.get_hier_block_message_io('out'):
+ message_port_register_hier_out("${pad['label']}")
+% endfor
+% elif generate_options == 'qt_gui':
+${class_name}::${class_name} () : QWidget(), top_block("display_qt") {
+ this->setWindowTitle("${title}");
+ // check_set_qss
+ // set icon
+ this->top_scroll_layout = new QVBoxLayout();
+ this->setLayout(this->top_scroll_layout);
+ this->top_scroll = new QScrollArea();
+ this->top_scroll->setFrameStyle(QFrame::NoFrame);
+ this->top_scroll_layout->addWidget(this->top_scroll);
+ this->top_scroll->setWidgetResizable(true);
+ this->top_widget = new QWidget();
+ this->top_scroll->setWidget(this->top_widget);
+ this->top_layout = new QVBoxLayout(this->top_widget);
+ this->top_grid_layout = new QGridLayout();
+ this->top_layout->addLayout(this->top_grid_layout);
+
+ this->settings = new QSettings("GNU Radio", "${class_name}");
+
+% endif
+
+% if flow_graph.get_option('thread_safe_setters'):
+## self._lock = threading.RLock()
+% endif
+
+% if blocks:
+// Blocks:
+% for blk, blk_make, declarations in blocks:
+ {
+ ${indent(blk_make)}
+## % if 'alias' in blk.params and blk.params['alias'].get_evaluated():
+## ${blk.name}.set_block_alias("${blk.params['alias'].get_evaluated()}")
+## % endif
+## % if 'affinity' in blk.params and blk.params['affinity'].get_evaluated():
+## ${blk.name}.set_processor_affinity("${blk.params['affinity'].get_evaluated()}")
+## % endif
+## % if len(blk.sources) > 0 and 'minoutbuf' in blk.params and int(blk.params['minoutbuf'].get_evaluated()) > 0:
+## ${blk.name}.set_min_output_buffer(${blk.params['minoutbuf'].get_evaluated()})
+## % endif
+## % if len(blk.sources) > 0 and 'maxoutbuf' in blk.params and int(blk.params['maxoutbuf'].get_evaluated()) > 0:
+## ${blk.name}.set_max_output_buffer(${blk.params['maxoutbuf'].get_evaluated()})
+## % endif
+ }
+% endfor
+% endif
+
+% if connections:
+// Connections:
+% for connection in connections:
+ ${connection.rstrip()};
+% endfor
+% endif
+}
+
+${class_name}::~${class_name} () {
+}
+
+// Callbacks:
+% for var in parameters + variables:
+${var.vtype} ${class_name}::get_${var.name} () const {
+ return this->${var.name};
+}
+
+void ${class_name}::set_${var.name} (${var.vtype} ${var.name}) {
+% if flow_graph.get_option('thread_safe_setters'):
+ ## with self._lock:
+ return;
+% else:
+ this->${var.name} = ${var.name};
+ % for callback in callbacks[var.name]:
+ ${callback};
+ % endfor
+% endif
+}
+% endfor
+
+int main (int argc, char **argv) {
+ % if parameters:
+ ## parse args
+ % endif
+ % if flow_graph.get_option('realtime_scheduling'):
+ if (enable_realtime_scheduling() != RT_OK) {
+ std::cout << "Error: failed to enable real-time scheduling." << std::endl;
+ }
+ % endif
+
+ % if generate_options == 'no_gui':
+ ${class_name}* top_block = new ${class_name}();
+ ## TODO: params
+ % if flow_graph.get_option('run_options') == 'prompt':
+ top_block->start();
+ % for m in monitors:
+ (top_block->${m.name}).start();
+ % endfor
+ std::cout << "Press Enter to quit: ";
+ std::cin.ignore();
+ top_block->stop();
+ % elif flow_graph.get_option('run_options') == 'run':
+ top_block->start();
+ % endif
+ % for m in monitors:
+ (top_block->${m.name}).start();
+ % endfor
+ top_block->wait();
+ % elif generate_options == 'qt_gui':
+ QApplication app(argc, argv);
+
+ ${class_name} *top_block = new ${class_name}();
+
+ top_block->start();
+ top_block->show();
+ app.exec();
+
+ % endif
+
+ return 0;
+}
+% if generate_options == 'qt_gui':
+#include "moc_${class_name}.cpp"
+% endif
diff --git a/grc/core/generator/cpp_templates/flow_graph.hpp.mako b/grc/core/generator/cpp_templates/flow_graph.hpp.mako
new file mode 100644
index 0000000000..0acd90734a
--- /dev/null
+++ b/grc/core/generator/cpp_templates/flow_graph.hpp.mako
@@ -0,0 +1,89 @@
+#ifndef ${flow_graph.get_option('id').upper()}_HPP
+#define ${flow_graph.get_option('id').upper()}_HPP
+/********************
+GNU Radio C++ Flow Graph Header File
+
+Title: ${title}
+% if flow_graph.get_option('author'):
+Author: ${flow_graph.get_option('author')}
+% endif
+% if flow_graph.get_option('description'):
+Description: ${flow_graph.get_option('description')}
+% endif
+Generated: ${generated_time}
+********************/
+
+/********************
+** Create includes
+********************/
+% for inc in includes:
+${inc}
+% endfor
+
+% if generate_options == 'qt_gui':
+#include <QVBoxLayout>
+#include <QScrollArea>
+#include <QWidget>
+#include <QGridLayout>
+#include <QSettings>
+% endif
+
+using namespace gr;
+
+<%
+ class_name = flow_graph.get_option('id')
+## TODO: param_str
+%>\
+
+% if generate_options == 'no_gui':
+class ${class_name} : public top_block {
+% elif generate_options.startswith('hb'):
+class ${class_name} : public hier_block2 {
+% elif generate_options == 'qt_gui':
+class ${class_name} : public QWidget, public top_block {
+ Q_OBJECT
+% endif
+
+private:
+% if generate_options == 'qt_gui':
+ QVBoxLayout *top_scroll_layout;
+ QScrollArea *top_scroll;
+ QWidget *top_widget;
+ QVBoxLayout *top_layout;
+ QGridLayout *top_grid_layout;
+ QSettings *settings;
+% endif
+
+
+% for block, make, declarations in blocks:
+ % if declarations:
+ ${declarations}
+ % endif
+% endfor
+
+% if parameters:
+// Parameters:
+% for param in parameters:
+ ${param.get_var_make()}
+% endfor
+% endif
+
+% if variables:
+// Variables:
+% for var in variables:
+ ${var.vtype} ${var.cpp_templates.render('var_make')}
+% endfor
+% endif
+
+public:
+ ${class_name}();
+ ## TODO: param_str
+ ~${class_name}();
+
+% for var in parameters + variables:
+ ${var.vtype} get_${var.name} () const;
+ void set_${var.name}(${var.vtype} ${var.name});
+% endfor
+
+};
+#endif
diff --git a/grc/core/generator/cpp_top_block.py b/grc/core/generator/cpp_top_block.py
new file mode 100644
index 0000000000..6991f22bf2
--- /dev/null
+++ b/grc/core/generator/cpp_top_block.py
@@ -0,0 +1,344 @@
+import codecs
+import operator
+import os
+import tempfile
+import textwrap
+import time
+import re
+
+from mako.template import Template
+
+from .. import Messages, blocks
+from ..Constants import TOP_BLOCK_FILE_MODE
+from .FlowGraphProxy import FlowGraphProxy
+from ..utils import expr_utils
+from .top_block import TopBlockGenerator
+
+DATA_DIR = os.path.dirname(__file__)
+
+HEADER_TEMPLATE = os.path.join(DATA_DIR, 'cpp_templates/flow_graph.hpp.mako')
+SOURCE_TEMPLATE = os.path.join(DATA_DIR, 'cpp_templates/flow_graph.cpp.mako')
+CMAKE_TEMPLATE = os.path.join(DATA_DIR, 'cpp_templates/CMakeLists.txt.mako')
+
+header_template = Template(filename=HEADER_TEMPLATE)
+source_template = Template(filename=SOURCE_TEMPLATE)
+cmake_template = Template(filename=CMAKE_TEMPLATE)
+
+
+class CppTopBlockGenerator(TopBlockGenerator):
+
+ def __init__(self, flow_graph, file_path):
+ """
+ Initialize the top block generator object.
+
+ Args:
+ flow_graph: the flow graph object
+ file_path: the path to write the file to
+ """
+
+ self._flow_graph = FlowGraphProxy(flow_graph)
+ self._generate_options = self._flow_graph.get_option('generate_options')
+
+ self._mode = TOP_BLOCK_FILE_MODE
+ dirname = os.path.dirname(file_path)
+ # Handle the case where the directory is read-only
+ # In this case, use the system's temp directory
+ if not os.access(dirname, os.W_OK):
+ dirname = tempfile.gettempdir()
+
+ filename = self._flow_graph.get_option('id')
+ self.file_path = os.path.join(dirname, filename)
+ self._dirname = dirname
+
+ def write(self):
+ """generate output and write it to files"""
+ self._warnings()
+
+ fg = self._flow_graph
+ self.title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title()
+ variables = fg.get_cpp_variables()
+ parameters = fg.get_parameters()
+ monitors = fg.get_monitors()
+ self._variable_types()
+
+ self.namespace = {
+ 'flow_graph': fg,
+ 'variables': variables,
+ 'parameters': parameters,
+ 'monitors': monitors,
+ 'generate_options': self._generate_options,
+ 'generated_time': time.ctime(),
+ }
+
+ if not os.path.exists(os.path.join(self.file_path, 'build')):
+ os.makedirs(os.path.join(self.file_path, 'build'))
+
+ for filename, data in self._build_cpp_header_code_from_template():
+ with codecs.open(filename, 'w', encoding='utf-8') as fp:
+ fp.write(data)
+
+ for filename, data in self._build_cpp_source_code_from_template():
+ with codecs.open(filename, 'w', encoding='utf-8') as fp:
+ fp.write(data)
+
+ if fg.get_option('gen_cmake') == 'On':
+ for filename, data in self._build_cmake_code_from_template():
+ with codecs.open(filename, 'w', encoding='utf-8') as fp:
+ fp.write(data)
+
+ def _build_cpp_source_code_from_template(self):
+ """
+ Convert the flow graph to a C++ source file.
+
+ Returns:
+ a string of C++ code
+ """
+ file_path = self.file_path + '/' + self._flow_graph.get_option('id') + '.cpp'
+
+ output = []
+
+ flow_graph_code = source_template.render(
+ title=self.title,
+ includes=self._includes(),
+ blocks=self._blocks(),
+ callbacks=self._callbacks(),
+ connections=self._connections(),
+ **self.namespace
+ )
+ # strip trailing white-space
+ flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
+ output.append((file_path, flow_graph_code))
+
+ return output
+
+
+ def _build_cpp_header_code_from_template(self):
+ """
+ Convert the flow graph to a C++ header file.
+
+ Returns:
+ a string of C++ code
+ """
+ file_path = self.file_path + '/' + self._flow_graph.get_option('id') + '.hpp'
+
+ output = []
+
+ flow_graph_code = header_template.render(
+ title=self.title,
+ includes=self._includes(),
+ blocks=self._blocks(),
+ callbacks=self._callbacks(),
+ connections=self._connections(),
+ **self.namespace
+ )
+ # strip trailing white-space
+ flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
+ output.append((file_path, flow_graph_code))
+
+ return output
+
+ def _build_cmake_code_from_template(self):
+ """
+ Convert the flow graph to a CMakeLists.txt file.
+
+ Returns:
+ a string of CMake code
+ """
+ filename = 'CMakeLists.txt'
+ file_path = os.path.join(self.file_path, filename)
+
+ output = []
+
+ flow_graph_code = cmake_template.render(
+ title=self.title,
+ includes=self._includes(),
+ blocks=self._blocks(),
+ callbacks=self._callbacks(),
+ connections=self._connections(),
+ links=self._links(),
+ **self.namespace
+ )
+ # strip trailing white-space
+ flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
+ output.append((file_path, flow_graph_code))
+
+ return output
+
+ def _links(self):
+ fg = self._flow_graph
+ links = fg.links()
+ seen = set()
+ output = []
+
+ for link_list in links:
+ if link_list:
+ for link in link_list:
+ seen.add(link)
+
+ return list(seen)
+
+ def _includes(self):
+ fg = self._flow_graph
+ includes = fg.includes()
+ seen = set()
+ output = []
+
+ def is_duplicate(l):
+ if l.startswith('#include') and l in seen:
+ return True
+ seen.add(line)
+ return False
+
+ for block_ in includes:
+ for include_ in block_:
+ if not include_:
+ continue
+ line = include_.rstrip()
+ if not is_duplicate(line):
+ output.append(line)
+
+ return output
+
+ def _blocks(self):
+ fg = self._flow_graph
+ parameters = fg.get_parameters()
+
+ # List of blocks not including variables and imports and parameters and disabled
+ def _get_block_sort_text(block):
+ code = block.cpp_templates.render('make').replace(block.name, ' ')
+ try:
+ code += block.params['gui_hint'].get_value() # Newer gui markup w/ qtgui
+ except:
+ pass
+ return code
+
+ blocks = [
+ b for b in fg.blocks
+ if b.enabled and not (b.get_bypassed() or b.is_import or b in parameters or b.key == 'options')
+ ]
+
+ blocks = expr_utils.sort_objects(blocks, operator.attrgetter('name'), _get_block_sort_text)
+ blocks_make = []
+ for block in blocks:
+ translations = block.cpp_templates.render('translations')
+ make = block.cpp_templates.render('make')
+ declarations = block.cpp_templates.render('declarations')
+
+ if translations:
+ # TODO: Would be nice to avoid using eval() here
+ translations_dict = eval(translations)
+ for key in translations_dict:
+ make = make.replace(key, translations_dict[key])
+ declarations = declarations.replace(key, translations_dict[key])
+ if make:
+ blocks_make.append((block, make, declarations))
+ elif 'qt' in block.key:
+ # The QT Widget blocks are technically variables,
+ # but they contain some code we don't want to miss
+ blocks_make.append(('', make, declarations))
+ return blocks_make
+
+ def _variable_types(self):
+ fg = self._flow_graph
+ variables = fg.get_cpp_variables()
+
+ for var in variables:
+ var.decide_type()
+
+ def _callbacks(self):
+ fg = self._flow_graph
+ variables = fg.get_cpp_variables()
+ parameters = fg.get_parameters()
+
+ # List of variable names
+ var_ids = [var.name for var in parameters + variables]
+
+ replace_dict = dict((var_id, 'this->' + var_id) for var_id in var_ids)
+
+ callbacks_all = []
+ for block in fg.iter_enabled_blocks():
+ callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_cpp_callbacks())
+
+ # Map var id to callbacks
+ def uses_var_id(callback):
+ used = expr_utils.get_variable_dependencies(callback, [var_id])
+ return used and ('this->' + var_id in callback) # callback might contain var_id itself
+
+ callbacks = {}
+ for var_id in var_ids:
+ callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id(callback)]
+
+ return callbacks
+
+ def _connections(self):
+ fg = self._flow_graph
+ templates = {key: Template(text)
+ for key, text in fg.parent_platform.cpp_connection_templates.items()}
+
+ def make_port_sig(port):
+ # TODO: make sense of this
+ if port.parent.key in ('pad_source', 'pad_sink'):
+ block = 'self'
+ key = fg.get_pad_port_global_key(port)
+ else:
+ block = 'this->' + port.parent_block.name
+ key = port.key
+
+ if not key.isdigit():
+ key = re.findall(r'\d+', key)[0]
+
+ return '{block}, {key}'.format(block=block, key=key)
+
+ connections = fg.get_enabled_connections()
+
+ # Get the virtual blocks and resolve their connections
+ connection_factory = fg.parent_platform.Connection
+ virtual = [c for c in connections if isinstance(c.source_block, blocks.VirtualSource)]
+ for connection in virtual:
+ sink = connection.sink_port
+ for source in connection.source_port.resolve_virtual_source():
+ resolved = connection_factory(fg.orignal_flowgraph, source, sink)
+ connections.append(resolved)
+ # Remove the virtual connection
+ connections.remove(connection)
+
+ # Bypassing blocks: Need to find all the enabled connections for the block using
+ # the *connections* object rather than get_connections(). Create new connections
+ # that bypass the selected block and remove the existing ones. This allows adjacent
+ # bypassed blocks to see the newly created connections to downstream blocks,
+ # allowing them to correctly construct bypass connections.
+ bypassed_blocks = fg.get_bypassed_blocks()
+ for block in bypassed_blocks:
+ # Get the upstream connection (off of the sink ports)
+ # Use *connections* not get_connections()
+ source_connection = [c for c in connections if c.sink_port == block.sinks[0]]
+ # The source connection should never have more than one element.
+ assert (len(source_connection) == 1)
+
+ # Get the source of the connection.
+ source_port = source_connection[0].source_port
+
+ # Loop through all the downstream connections
+ for sink in (c for c in connections if c.source_port == block.sources[0]):
+ if not sink.enabled:
+ # Ignore disabled connections
+ continue
+ connection = connection_factory(fg.orignal_flowgraph, source_port, sink.sink_port)
+ connections.append(connection)
+ # Remove this sink connection
+ connections.remove(sink)
+ # Remove the source connection
+ connections.remove(source_connection[0])
+
+ # List of connections where each endpoint is enabled (sorted by domains, block names)
+ def by_domain_and_blocks(c):
+ return c.type, c.source_block.name, c.sink_block.name
+
+ rendered = []
+ for con in sorted(connections, key=by_domain_and_blocks):
+ template = templates[con.type]
+ code = template.render(make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
+ code = 'this->' + code
+ rendered.append(code)
+
+ return rendered
diff --git a/grc/core/generator/top_block.py b/grc/core/generator/top_block.py
index b5661762c0..cab1bf71cb 100644
--- a/grc/core/generator/top_block.py
+++ b/grc/core/generator/top_block.py
@@ -13,8 +13,10 @@ from .FlowGraphProxy import FlowGraphProxy
from ..utils import expr_utils
DATA_DIR = os.path.dirname(__file__)
-FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.py.mako')
-flow_graph_template = Template(filename=FLOW_GRAPH_TEMPLATE)
+
+PYTHON_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.py.mako')
+
+python_template = Template(filename=PYTHON_TEMPLATE)
class TopBlockGenerator(object):
@@ -65,6 +67,21 @@ class TopBlockGenerator(object):
"""generate output and write it to files"""
self._warnings()
+ fg = self._flow_graph
+ self.title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title()
+ variables = fg.get_variables()
+ parameters = fg.get_parameters()
+ monitors = fg.get_monitors()
+
+ self.namespace = {
+ 'flow_graph': fg,
+ 'variables': variables,
+ 'parameters': parameters,
+ 'monitors': monitors,
+ 'generate_options': self._generate_options,
+ 'generated_time': time.ctime(),
+ }
+
for filename, data in self._build_python_code_from_template():
with codecs.open(filename, 'w', encoding='utf-8') as fp:
fp.write(data)
@@ -106,13 +123,13 @@ class TopBlockGenerator(object):
'generated_time': time.ctime(),
'version': platform.config.version
}
- flow_graph_code = flow_graph_template.render(
+ flow_graph_code = python_template.render(
title=title,
imports=self._imports(),
blocks=self._blocks(),
callbacks=self._callbacks(),
connections=self._connections(),
- **namespace
+ **self.namespace
)
# strip trailing white-space
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
@@ -196,7 +213,9 @@ class TopBlockGenerator(object):
# List of variable names
var_ids = [var.name for var in parameters + variables]
+
replace_dict = dict((var_id, 'self.' + var_id) for var_id in var_ids)
+
callbacks_all = []
for block in fg.iter_enabled_blocks():
callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_callbacks())
@@ -204,7 +223,7 @@ class TopBlockGenerator(object):
# Map var id to callbacks
def uses_var_id(callback):
used = expr_utils.get_variable_dependencies(callback, [var_id])
- return used and 'self.' + var_id in callback # callback might contain var_id itself
+ return used and (('self.' + var_id in callback) or ('this->' + var_id in callback)) # callback might contain var_id itself
callbacks = {}
for var_id in var_ids:
@@ -218,6 +237,7 @@ class TopBlockGenerator(object):
for key, text in fg.parent_platform.connection_templates.items()}
def make_port_sig(port):
+ # TODO: make sense of this
if port.parent.key in ('pad_source', 'pad_sink'):
block = 'self'
key = fg.get_pad_port_global_key(port)
@@ -282,6 +302,7 @@ class TopBlockGenerator(object):
for con in sorted(connections, key=by_domain_and_blocks):
template = templates[con.type]
code = template.render(make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
+ code = 'self.' + code
rendered.append(code)
return rendered
diff --git a/grc/core/platform.py b/grc/core/platform.py
index fc86be927d..5449a117ad 100644
--- a/grc/core/platform.py
+++ b/grc/core/platform.py
@@ -22,6 +22,7 @@ from collections import namedtuple
import os
import logging
from itertools import chain
+import re
import six
from six.moves import range
@@ -61,6 +62,7 @@ class Platform(Element):
self.blocks = self.block_classes
self.domains = {}
self.connection_templates = {}
+ self.cpp_connection_templates = {}
self._block_categories = {}
self._auto_hier_block_generate_chain = set()
@@ -138,6 +140,7 @@ class Platform(Element):
self.blocks.clear()
self.domains.clear()
self.connection_templates.clear()
+ self.cpp_connection_templates.clear()
self._block_categories.clear()
# # FIXME: remove this as soon as converter is stable
@@ -277,6 +280,7 @@ class Platform(Element):
continue
connection_id = str(source_id), str(sink_id)
self.connection_templates[connection_id] = connection.get('connect', '')
+ self.cpp_connection_templates[connection_id] = connection.get('cpp_connect', '')
def load_category_tree_description(self, data, file_path):
"""Parse category tree file and add it to list"""
@@ -362,7 +366,10 @@ class Platform(Element):
('metadata:\n', '\nmetadata:\n'),
]
for r in replace:
+ # the purpose of using re.sub() here is to avoid corrupting the .grc file
+ out = re.sub(r'(nconnections)(?<!\\)', 'NCONNECTIONS', out)
out = out.replace(*r)
+ out = re.sub(r'(NCONNECTIONS)(?<!\\)', 'nconnections', out)
with open(filename, 'w', encoding='utf-8') as fp:
fp.write(out)
@@ -377,6 +384,16 @@ class Platform(Element):
return [(value, name, value == generate_mode_default)
for value, name in zip(param['options'], param['option_labels'])]
+ def get_output_language(self):
+ for param in self.block_classes['options'].parameters_data:
+ if param.get('id') == 'output_language':
+ break
+ else:
+ return []
+ output_language_default = param.get('default')
+ return [(value, name, value == output_language_default)
+ for value, name in zip(param['options'], param['option_labels'])]
+
##############################################
# Factories
##############################################
diff --git a/grc/core/schema_checker/block.py b/grc/core/schema_checker/block.py
index 92ed48758c..91ce906df2 100644
--- a/grc/core/schema_checker/block.py
+++ b/grc/core/schema_checker/block.py
@@ -35,6 +35,14 @@ TEMPLATES_SCHEME = expand(
make=str_,
callbacks=list,
)
+CPP_TEMPLATES_SCHEME = expand(
+ includes=list,
+ declarations=str_,
+ make=str_,
+ callbacks=list,
+ link=list,
+ translations=dict,
+)
BLOCK_SCHEME = expand(
id=Spec(types=str_, required=True, item_scheme=None),
label=str_,
@@ -49,6 +57,7 @@ BLOCK_SCHEME = expand(
value=str_,
templates=Spec(types=dict, required=False, item_scheme=TEMPLATES_SCHEME),
+ cpp_templates=Spec(types=dict, required=False, item_scheme=CPP_TEMPLATES_SCHEME),
documentation=str_,
grc_source=str_,
diff --git a/grc/gui/Executor.py b/grc/gui/Executor.py
index efed54b042..79bb29f101 100644
--- a/grc/gui/Executor.py
+++ b/grc/gui/Executor.py
@@ -44,7 +44,11 @@ class ExecFlowGraphThread(threading.Thread):
self.update_callback = callback
try:
- self.process = self.page.process = self._popen()
+ if self.flow_graph.get_option('output_language') == 'python':
+ self.process = self.page.process = self._popen()
+ elif self.flow_graph.get_option('output_language') == 'cpp':
+ self.process = self.page.process = self._cpp_popen()
+
self.update_callback()
self.start()
except Exception as e:
@@ -75,6 +79,74 @@ class ExecFlowGraphThread(threading.Thread):
shell=False, universal_newlines=True
)
+ def _cpp_run_cmake(self):
+ """
+ Generate and compile this C++ flow graph.
+ """
+ generator = self.page.get_generator()
+
+ xterm_executable = find_executable(self.xterm_executable)
+
+ dirname = generator.file_path
+ builddir = os.path.join(dirname, 'build')
+
+ run_command_args = [ 'cmake', '..' ]
+ Messages.send_start_exec(' '.join(run_command_args))
+
+ return subprocess.Popen(
+ args=run_command_args,
+ cwd=builddir,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ shell=False, universal_newlines=True
+ )
+
+ def _cpp_compile(self):
+ """
+ Compile this C++ flow graph.
+ """
+ generator = self.page.get_generator()
+
+ xterm_executable = find_executable(self.xterm_executable)
+
+ dirname = generator.file_path
+ builddir = os.path.join(dirname, 'build')
+
+ run_command_args = [ 'make' ]
+ Messages.send_start_exec(' '.join(run_command_args))
+
+ return subprocess.Popen(
+ args=run_command_args,
+ cwd=builddir,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ shell=False, universal_newlines=True
+ )
+
+
+ def _cpp_popen(self):
+ """
+ Execute this C++ flow graph after generating and compiling it.
+ """
+ generator = self.page.get_generator()
+ run_command = generator.file_path + '/build/' + self.flow_graph.get_option('id')
+
+ if os.path.isfile(run_command):
+ os.remove(run_command)
+
+ xterm_executable = find_executable(self.xterm_executable)
+ process = self._cpp_run_cmake()
+ process.wait()
+ process = self._cpp_compile()
+ process.wait()
+
+ run_command_args = [xterm_executable, '-e', run_command]
+ Messages.send_start_exec(' '.join(run_command_args))
+
+ return subprocess.Popen(
+ args=run_command_args,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ shell=False, universal_newlines=True
+ )
+
def run(self):
"""
Wait on the executing process by reading from its stdout.
diff --git a/grc/tests/test_cpp.py b/grc/tests/test_cpp.py
new file mode 100644
index 0000000000..fc52218477
--- /dev/null
+++ b/grc/tests/test_cpp.py
@@ -0,0 +1,18 @@
+
+
+from argparse import Namespace
+from os import path
+import tempfile
+import subprocess
+
+from .test_compiler import *
+
+def test_cpp():
+ test_compiler('test_cpp.grc')
+ if not os.path.exists('test_cpp/build'):
+ os.makedirs('directory')
+ return_code = subprocess.Popen('cmake ..', cwd='./build/')
+ assert(return_code == 0)
+ return_code = subprocess.Popen('make', cwd='./build/')
+ assert(return_code == 0)
+