summaryrefslogtreecommitdiff
path: root/grc
diff options
context:
space:
mode:
Diffstat (limited to 'grc')
-rwxr-xr-xgrc/compiler.py76
-rw-r--r--grc/core/Config.py4
-rw-r--r--grc/core/FlowGraph.py15
-rw-r--r--grc/core/Param.py2
-rw-r--r--grc/core/Platform.py23
-rw-r--r--grc/core/generator/flow_graph.tmpl3
-rw-r--r--grc/core/utils/epy_block_io.py2
-rw-r--r--grc/core/utils/shlex.py47
-rw-r--r--grc/gui/Application.py3
-rw-r--r--grc/gui/Constants.py10
-rw-r--r--grc/gui/Dialogs.py2
-rw-r--r--grc/gui/Executor.py35
-rw-r--r--grc/gui/ParamWidgets.py4
-rw-r--r--grc/gui/PropsDialog.py4
-rw-r--r--grc/gui/Utils.py10
-rw-r--r--grc/gui/VariableEditor.py10
-rw-r--r--grc/gui/canvas/block.py3
-rw-r--r--grc/scripts/CMakeLists.txt2
-rwxr-xr-xgrc/scripts/gnuradio-companion34
-rwxr-xr-xgrc/scripts/grcc64
-rw-r--r--grc/tests/resources/test_compiler.grc253
-rw-r--r--grc/tests/test_compiler.py38
22 files changed, 565 insertions, 79 deletions
diff --git a/grc/compiler.py b/grc/compiler.py
new file mode 100755
index 0000000000..b2361b86eb
--- /dev/null
+++ b/grc/compiler.py
@@ -0,0 +1,76 @@
+# Copyright 2016 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+from __future__ import print_function, absolute_import
+
+import argparse
+import os
+import subprocess
+
+from gnuradio import gr
+
+from .core import Messages
+from .core.Platform import Platform
+
+
+def argument_parser():
+ parser = argparse.ArgumentParser(description=(
+ "Compile a GRC file (.grc) into a GNU Radio Python program and run it."
+ ))
+ parser.add_argument("-o", "--output", metavar='DIR', default='.',
+ help="Output directory for compiled program [default=%(default)s]")
+ parser.add_argument("-u", "--user-lib-dir", action='store_true', default=False,
+ help="Output to default hier_block library (overwrites -o)")
+ parser.add_argument("-r", "--run", action="store_true", default=False,
+ help="Run the program after compiling [default=%(default)s]")
+ parser.add_argument(metavar="GRC_FILE", dest='grc_files', nargs='+',
+ help=".grc file to compile")
+ return parser
+
+
+def main(args=None):
+ args = args or argument_parser().parse_args()
+
+ platform = Platform(
+ name='GNU Radio Companion Compiler',
+ prefs=gr.prefs(),
+ version=gr.version(),
+ version_parts=(gr.major_version(), gr.api_version(), gr.minor_version())
+ )
+ out_dir = args.output if not args.user_lib_dir else platform.config.hier_block_lib_dir
+ if os.path.exists(out_dir):
+ pass # all is well
+ elif args.save_to_lib:
+ os.mkdir(out_dir) # create missing hier_block lib directory
+ else:
+ exit('Error: Invalid output directory')
+
+ Messages.send_init(platform)
+ flow_graph = file_path = None
+ for grc_file in args.grc_files:
+ os.path.exists(grc_file) or exit('Error: missing ' + grc_file)
+ Messages.send('\n')
+
+ flow_graph, file_path = platform.load_and_generate_flow_graph(
+ os.path.abspath(grc_file), os.path.abspath(out_dir))
+ if not file_path:
+ exit('Compilation error')
+ if file_path and args.run:
+ run_command_args = flow_graph.get_run_command(file_path, split=True)
+ subprocess.call(run_command_args)
diff --git a/grc/core/Config.py b/grc/core/Config.py
index 3455a382c1..cc199a348f 100644
--- a/grc/core/Config.py
+++ b/grc/core/Config.py
@@ -32,10 +32,12 @@ class Config(object):
hier_block_lib_dir = os.environ.get('GRC_HIER_PATH', Constants.DEFAULT_HIER_BLOCK_LIB_DIR)
- def __init__(self, version, version_parts=None, prefs=None):
+ def __init__(self, version, version_parts=None, name=None, prefs=None):
self._gr_prefs = prefs if prefs else DummyPrefs()
self.version = version
self.version_parts = version_parts or version[1:].split('-', 1)[0].split('.')[:3]
+ if name:
+ self.name = name
@property
def block_paths(self):
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 18a5778015..bf5bf6d93e 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -22,11 +22,12 @@ import time
import re
from operator import methodcaller
import collections
+import sys
from . import Messages
from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
from .Element import Element
-from .utils import expr_utils
+from .utils import expr_utils, shlex
_parameter_matcher = re.compile('^(parameter)$')
_monitors_searcher = re.compile('(ctrlport_monitor)')
@@ -181,6 +182,16 @@ class FlowGraph(Element):
"""
return self._options_block.get_param(key).get_evaluated()
+ def get_run_command(self, file_path, split=False):
+ run_command = self.get_option('run_command')
+ try:
+ run_command = run_command.format(
+ python=shlex.quote(sys.executable),
+ filename=shlex.quote(file_path))
+ return shlex.split(run_command) if split else run_command
+ except Exception as e:
+ raise ValueError("Can't parse run command {!r}: {}".format(run_command, e))
+
##############################################
# Access Elements
##############################################
@@ -392,7 +403,7 @@ class FlowGraph(Element):
cwd=self.grc_file_path
)
if file_path: # grc file found. load and get block
- self.parent_platform.load_and_generate_flow_graph(file_path)
+ self.parent_platform.load_and_generate_flow_graph(file_path, hier_only=True)
block = self.new_block(key) # can be None
if not block: # looks like this block key cannot be found
diff --git a/grc/core/Param.py b/grc/core/Param.py
index 31393b1d79..9544d79764 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -29,7 +29,7 @@ from . import Constants
from .Element import Element, nop_write
# Blacklist certain ids, its not complete, but should help
-ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'math', 'firdes'] + dir(builtins)
+ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(builtins)
try:
from gnuradio import gr
ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_'))
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 1e43271dc2..73937f1299 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -90,42 +90,43 @@ class Platform(Element):
if os.path.exists(os.path.normpath(file_path)):
return file_path
- def load_and_generate_flow_graph(self, file_path):
+ def load_and_generate_flow_graph(self, file_path, out_path=None, hier_only=False):
"""Loads a flow graph from file and generates it"""
Messages.set_indent(len(self._auto_hier_block_generate_chain))
- Messages.send('>>> Loading: %r\n' % file_path)
+ Messages.send('>>> Loading: {}\n'.format(file_path))
if file_path in self._auto_hier_block_generate_chain:
Messages.send(' >>> Warning: cyclic hier_block dependency\n')
- return False
+ return None, None
self._auto_hier_block_generate_chain.add(file_path)
try:
flow_graph = self.get_new_flow_graph()
flow_graph.grc_file_path = file_path
- # Other, nested higiter_blocks might be auto-loaded here
+ # Other, nested hier_blocks might be auto-loaded here
flow_graph.import_data(self.parse_flow_graph(file_path))
flow_graph.rewrite()
flow_graph.validate()
if not flow_graph.is_valid():
raise Exception('Flowgraph invalid')
- if not flow_graph.get_option('generate_options').startswith('hb'):
+ if hier_only and not flow_graph.get_option('generate_options').startswith('hb'):
raise Exception('Not a hier block')
except Exception as e:
Messages.send('>>> Load Error: {}: {}\n'.format(file_path, str(e)))
- return False
+ return None, None
finally:
self._auto_hier_block_generate_chain.discard(file_path)
Messages.set_indent(len(self._auto_hier_block_generate_chain))
try:
- Messages.send('>>> Generating: {}\n'.format(file_path))
- generator = self.Generator(flow_graph, file_path)
+ generator = self.Generator(flow_graph, out_path or file_path)
+ Messages.send('>>> Generating: {}\n'.format(generator.file_path))
generator.write()
except Exception as e:
Messages.send('>>> Generate Error: {}: {}\n'.format(file_path, str(e)))
- return False
+ return None, None
- self.load_block_xml(generator.file_path_xml)
- return True
+ if flow_graph.get_option('generate_options').startswith('hb'):
+ self.load_block_xml(generator.file_path_xml)
+ return flow_graph, generator.file_path
def build_block_library(self):
"""load the blocks and block tree from the search paths"""
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
index b8913bb087..202362c925 100644
--- a/grc/core/generator/flow_graph.tmpl
+++ b/grc/core/generator/flow_graph.tmpl
@@ -80,7 +80,7 @@ $imp
#set $class_name = $flow_graph.get_option('id')
#set $param_str = ', '.join(['self'] + ['%s=%s'%(param.get_id(), param.get_make()) for param in $parameters])
#if $generate_options == 'qt_gui'
-
+from gnuradio import qtgui
class $(class_name)(gr.top_block, Qt.QWidget):
@@ -88,6 +88,7 @@ class $(class_name)(gr.top_block, Qt.QWidget):
gr.top_block.__init__(self, "$title")
Qt.QWidget.__init__(self)
self.setWindowTitle("$title")
+ qtgui.util.check_set_qss()
try:
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
except:
diff --git a/grc/core/utils/epy_block_io.py b/grc/core/utils/epy_block_io.py
index 7a2006a833..fc631203e3 100644
--- a/grc/core/utils/epy_block_io.py
+++ b/grc/core/utils/epy_block_io.py
@@ -24,7 +24,7 @@ def _ports(sigs, msgs):
for i, dtype in enumerate(sigs):
port_type = TYPE_MAP.get(dtype.name, None)
if not port_type:
- raise ValueError("Can't map {0:!r} to GRC port type".format(dtype))
+ raise ValueError("Can't map {0!r} to GRC port type".format(dtype))
ports.append((str(i), port_type))
for msg_key in msgs:
if msg_key == 'system':
diff --git a/grc/core/utils/shlex.py b/grc/core/utils/shlex.py
new file mode 100644
index 0000000000..6b620fa396
--- /dev/null
+++ b/grc/core/utils/shlex.py
@@ -0,0 +1,47 @@
+# Copyright 2016 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+from __future__ import absolute_import
+
+import re
+import shlex
+
+# back port from python3
+
+_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
+
+
+def _shlex_quote(s):
+ """Return a shell-escaped version of the string *s*."""
+ if not s:
+ return "''"
+ if _find_unsafe(s) is None:
+ return s
+
+ # use single quotes, and put single quotes into double quotes
+ # the string $'b is then quoted as '$'"'"'b'
+ return "'" + s.replace("'", "'\"'\"'") + "'"
+
+
+if not hasattr(shlex, 'quote'):
+ quote = _shlex_quote
+else:
+ quote = shlex.quote
+
+split = shlex.split
diff --git a/grc/gui/Application.py b/grc/gui/Application.py
index 5c643eafa1..e2290b3401 100644
--- a/grc/gui/Application.py
+++ b/grc/gui/Application.py
@@ -519,8 +519,9 @@ class Application(Gtk.Application):
elif action == Actions.FLOW_GRAPH_NEW:
main.new_page()
if args:
+ flow_graph = main.get_flow_graph()
flow_graph._options_block.get_param('generate_options').set_value(args[0])
- flow_graph_update()
+ flow_graph_update(flow_graph)
elif action == Actions.FLOW_GRAPH_OPEN:
file_paths = args if args else FileDialogs.OpenFlowGraph(main, page.file_path).run()
if file_paths: # Open a new page for each file, show only the first
diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py
index 0a555b37c9..01ea23ed3e 100644
--- a/grc/gui/Constants.py
+++ b/grc/gui/Constants.py
@@ -19,13 +19,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from __future__ import absolute_import
-from gi.repository import Gtk
+from gi.repository import Gtk, Gdk
from ..core.Constants import *
# default path for the open/save dialogs
-DEFAULT_FILE_PATH = os.getcwd()
+DEFAULT_FILE_PATH = os.getcwd() if os.name != 'nt' else os.path.expanduser("~/Documents")
FILE_EXTENSION = '.grc'
# name for new/unsaved flow graphs
@@ -101,6 +101,12 @@ Please consider contacting OOT module maintainer for any block in here \
and kindly ask to update their GRC Block Descriptions or Block Tree to include a module name."""
+# _SCREEN = Gdk.Screen.get_default()
+# _SCREEN_RESOLUTION = _SCREEN.get_resolution() if _SCREEN else -1
+# DPI_SCALING = _SCREEN_RESOLUTION / 96.0 if _SCREEN_RESOLUTION > 0 else 1.0
+DPI_SCALING = 1.0 # todo: figure out the GTK3 way (maybe cairo does this for us
+
+
def update_font_size(font_size):
global PORT_SEPARATION, BLOCK_FONT, PORT_FONT, PARAM_FONT, FONT_SIZE
diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py
index 0eb88952b7..09ecd48a13 100644
--- a/grc/gui/Dialogs.py
+++ b/grc/gui/Dialogs.py
@@ -90,7 +90,7 @@ class TextDisplay(SimpleTextDisplay):
# for each \b delete one char from the buffer
back_count = 0
start_iter = self.get_buffer().get_end_iter()
- while line[back_count] == '\b':
+ while len(line) > back_count and line[back_count] == '\b':
# stop at the beginning of a line
if not start_iter.starts_line():
start_iter.backward_char()
diff --git a/grc/gui/Executor.py b/grc/gui/Executor.py
index 7168c9ef46..7c01d921bc 100644
--- a/grc/gui/Executor.py
+++ b/grc/gui/Executor.py
@@ -16,12 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from __future__ import absolute_import
+
import os
-import threading
import shlex
import subprocess
-import sys
-import re
+import threading
from distutils.spawn import find_executable
from gi.repository import GLib
@@ -39,6 +38,7 @@ class ExecFlowGraphThread(threading.Thread):
threading.Thread.__init__(self)
self.page = flow_graph_page # store page and dont use main window calls in run
+ self.flow_graph = self.page.get_flow_graph()
self.xterm_executable = xterm_executable
self.update_callback = callback
@@ -54,16 +54,9 @@ class ExecFlowGraphThread(threading.Thread):
"""
Execute this python flow graph.
"""
- run_command = self.page.flow_graph.get_option('run_command')
generator = self.page.get_generator()
-
- try:
- run_command = run_command.format(
- python=shlex_quote(sys.executable),
- filename=shlex_quote(generator.file_path))
- run_command_args = shlex.split(run_command)
- except Exception as e:
- raise ValueError("Can't parse run command {!r}: {}".format(run_command, e))
+ run_command = self.flow_graph.get_run_command(generator.file_path)
+ run_command_args = shlex.split(run_command)
# When in no gui mode on linux, use a graphical terminal (looks nice)
xterm_executable = find_executable(self.xterm_executable)
@@ -99,21 +92,3 @@ class ExecFlowGraphThread(threading.Thread):
Messages.send_end_exec(self.process.returncode)
self.page.process = None
self.update_callback()
-
-
-###########################################################
-# back-port from python3
-###########################################################
-_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
-
-
-def shlex_quote(s):
- """Return a shell-escaped version of the string *s*."""
- if not s:
- return "''"
- if _find_unsafe(s) is None:
- return s
-
- # use single quotes, and put single quotes into double quotes
- # the string $'b is then quoted as '$'"'"'b'
- return "'" + s.replace("'", "'\"'\"'") + "'"
diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py
index e5657c288e..b79a85623f 100644
--- a/grc/gui/ParamWidgets.py
+++ b/grc/gui/ParamWidgets.py
@@ -20,7 +20,7 @@ import os
from gi.repository import Gtk, Gdk
-from . import Colors
+from . import Colors, Utils
class InputParam(Gtk.HBox):
@@ -35,7 +35,7 @@ class InputParam(Gtk.HBox):
self._editing_callback = editing_callback
self.label = Gtk.Label()
- self.label.set_size_request(150, -1)
+ self.label.set_size_request(Utils.scale_scalar(150), -1)
self.label.show()
self.pack_start(self.label, False, False, 0)
diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py
index 3a0f6ae6de..ca1e3c5296 100644
--- a/grc/gui/PropsDialog.py
+++ b/grc/gui/PropsDialog.py
@@ -51,7 +51,9 @@ class PropsDialog(Gtk.Dialog):
Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY,
)
self.set_response_sensitive(Gtk.ResponseType.APPLY, False)
- self.set_size_request(Constants.MIN_DIALOG_WIDTH, Constants.MIN_DIALOG_HEIGHT)
+ self.set_size_request(*Utils.scale(
+ (Constants.MIN_DIALOG_WIDTH, Constants.MIN_DIALOG_HEIGHT)
+ ))
self._block = block
self._hash = 0
diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py
index 782a7e3a01..38fde80465 100644
--- a/grc/gui/Utils.py
+++ b/grc/gui/Utils.py
@@ -143,3 +143,13 @@ def make_screenshot(flow_graph, file_path, transparent_bg=False):
if file_path.endswith('.pdf') or file_path.endswith('.svg'):
cr.show_page()
psurf.finish()
+
+
+def scale(coor, reverse=False):
+ factor = Constants.DPI_SCALING if not reverse else 1 / Constants.DPI_SCALING
+ return tuple(int(x * factor) for x in coor)
+
+
+def scale_scalar(coor, reverse=False):
+ factor = Constants.DPI_SCALING if not reverse else 1 / Constants.DPI_SCALING
+ return int(coor * factor)
diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py
index 44dd2923eb..484395be8c 100644
--- a/grc/gui/VariableEditor.py
+++ b/grc/gui/VariableEditor.py
@@ -21,7 +21,7 @@ from __future__ import absolute_import
from gi.repository import Gtk, Gdk, GObject
-from . import Actions, Constants
+from . import Actions, Constants, Utils
BLOCK_INDEX = 0
ID_INDEX = 1
@@ -114,9 +114,9 @@ class VariableEditor(Gtk.VBox):
id_column = Gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX)
id_column.set_name("id")
id_column.set_resizable(True)
- id_column.set_max_width(300)
- id_column.set_min_width(80)
- id_column.set_fixed_width(100)
+ id_column.set_max_width(Utils.scale_scalar(300))
+ id_column.set_min_width(Utils.scale_scalar(80))
+ id_column.set_fixed_width(Utils.scale_scalar(100))
id_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
id_column.set_cell_data_func(self.id_cell, self.set_properties)
self.id_column = id_column
@@ -132,7 +132,7 @@ class VariableEditor(Gtk.VBox):
value_column.set_name("value")
value_column.set_resizable(False)
value_column.set_expand(True)
- value_column.set_min_width(100)
+ value_column.set_min_width(Utils.scale_scalar(100))
value_column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
value_column.set_cell_data_func(self.value_cell, self.set_value)
self.value_column = value_column
diff --git a/grc/gui/canvas/block.py b/grc/gui/canvas/block.py
index 7e28a21fc2..fc8a533397 100644
--- a/grc/gui/canvas/block.py
+++ b/grc/gui/canvas/block.py
@@ -69,7 +69,7 @@ class Block(CoreBlock, Drawable):
Returns:
the coordinate tuple (x, y) or (0, 0) if failure
"""
- return self.states['_coordinate']
+ return Utils.scale(self.states['_coordinate'])
@coordinate.setter
def coordinate(self, coor):
@@ -79,6 +79,7 @@ class Block(CoreBlock, Drawable):
Args:
coor: the coordinate tuple (x, y)
"""
+ coor = Utils.scale(coor, reverse=True)
if Actions.TOGGLE_SNAP_TO_GRID.get_active():
offset_x, offset_y = (0, self.height / 2) if self.is_horizontal() else (self.height / 2, 0)
coor = (
diff --git a/grc/scripts/CMakeLists.txt b/grc/scripts/CMakeLists.txt
index 9751952118..20366e0212 100644
--- a/grc/scripts/CMakeLists.txt
+++ b/grc/scripts/CMakeLists.txt
@@ -19,7 +19,7 @@
########################################################################
GR_PYTHON_INSTALL(
- PROGRAMS gnuradio-companion
+ PROGRAMS gnuradio-companion grcc
DESTINATION ${GR_RUNTIME_DIR}
)
diff --git a/grc/scripts/gnuradio-companion b/grc/scripts/gnuradio-companion
index 21d989164f..c1ffdf28bc 100755
--- a/grc/scripts/gnuradio-companion
+++ b/grc/scripts/gnuradio-companion
@@ -1,22 +1,20 @@
#!/usr/bin/env python
-"""
-Copyright 2016 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-"""
+# Copyright 2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
import os
import sys
diff --git a/grc/scripts/grcc b/grc/scripts/grcc
new file mode 100755
index 0000000000..c3a53a91a6
--- /dev/null
+++ b/grc/scripts/grcc
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# Copyright 2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+import os
+import sys
+
+
+GR_IMPORT_ERROR_MESSAGE = """\
+Cannot import gnuradio.
+
+Is the model path environment variable set correctly?
+ All OS: PYTHONPATH
+
+Is the library path environment variable set correctly?
+ Linux: LD_LIBRARY_PATH
+ Windows: PATH
+ MacOSX: DYLD_LIBRARY_PATH
+"""
+
+
+def die(error, message):
+ msg = "{0}\n\n({1})".format(message, error)
+ exit(type(error).__name__ + '\n\n' + msg)
+
+
+def check_gnuradio_import():
+ try:
+ from gnuradio import gr
+ except ImportError as err:
+ die(err, GR_IMPORT_ERROR_MESSAGE)
+
+
+def run_main():
+ script_path = os.path.dirname(os.path.abspath(__file__))
+ source_tree_subpath = "/grc/scripts"
+
+ if not script_path.endswith(source_tree_subpath):
+ # run the installed version
+ from gnuradio.grc.compiler import main
+ else:
+ print("Running from source tree")
+ sys.path.insert(1, script_path[:-len(source_tree_subpath)])
+ from grc.compiler import main
+ exit(main())
+
+
+if __name__ == '__main__':
+ check_gnuradio_import()
+ run_main()
diff --git a/grc/tests/resources/test_compiler.grc b/grc/tests/resources/test_compiler.grc
new file mode 100644
index 0000000000..cc56acedca
--- /dev/null
+++ b/grc/tests/resources/test_compiler.grc
@@ -0,0 +1,253 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?grc format='1' created='3.7.11'?>
+<flow_graph>
+ <timestamp>Thu Sep 15 12:56:40 2016</timestamp>
+ <block>
+ <key>options</key>
+ <param>
+ <key>author</key>
+ <value></value>
+ </param>
+ <param>
+ <key>window_size</key>
+ <value></value>
+ </param>
+ <param>
+ <key>category</key>
+ <value>[GRC Hier Blocks]</value>
+ </param>
+ <param>
+ <key>comment</key>
+ <value></value>
+ </param>
+ <param>
+ <key>description</key>
+ <value></value>
+ </param>
+ <param>
+ <key>_enabled</key>
+ <value>1</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(8, 8)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>generate_options</key>
+ <value>no_gui</value>
+ </param>
+ <param>
+ <key>hier_block_src_path</key>
+ <value>.:</value>
+ </param>
+ <param>
+ <key>id</key>
+ <value>top_block</value>
+ </param>
+ <param>
+ <key>max_nouts</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>qt_qss_theme</key>
+ <value></value>
+ </param>
+ <param>
+ <key>realtime_scheduling</key>
+ <value></value>
+ </param>
+ <param>
+ <key>run_command</key>
+ <value>{python} -u {filename}</value>
+ </param>
+ <param>
+ <key>run_options</key>
+ <value>run</value>
+ </param>
+ <param>
+ <key>run</key>
+ <value>True</value>
+ </param>
+ <param>
+ <key>thread_safe_setters</key>
+ <value></value>
+ </param>
+ <param>
+ <key>title</key>
+ <value></value>
+ </param>
+ </block>
+ <block>
+ <key>blocks_add_const_vxx</key>
+ <param>
+ <key>alias</key>
+ <value></value>
+ </param>
+ <param>
+ <key>comment</key>
+ <value></value>
+ </param>
+ <param>
+ <key>const</key>
+ <value>1</value>
+ </param>
+ <param>
+ <key>affinity</key>
+ <value></value>
+ </param>
+ <param>
+ <key>_enabled</key>
+ <value>True</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(360, 28)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>id</key>
+ <value>blocks_add_const_vxx_0</value>
+ </param>
+ <param>
+ <key>type</key>
+ <value>complex</value>
+ </param>
+ <param>
+ <key>maxoutbuf</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>minoutbuf</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>vlen</key>
+ <value>1</value>
+ </param>
+ </block>
+ <block>
+ <key>blocks_null_sink</key>
+ <param>
+ <key>alias</key>
+ <value></value>
+ </param>
+ <param>
+ <key>bus_conns</key>
+ <value>[[0,],]</value>
+ </param>
+ <param>
+ <key>comment</key>
+ <value></value>
+ </param>
+ <param>
+ <key>affinity</key>
+ <value></value>
+ </param>
+ <param>
+ <key>_enabled</key>
+ <value>True</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(504, 32)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>id</key>
+ <value>blocks_null_sink_0</value>
+ </param>
+ <param>
+ <key>type</key>
+ <value>complex</value>
+ </param>
+ <param>
+ <key>num_inputs</key>
+ <value>1</value>
+ </param>
+ <param>
+ <key>vlen</key>
+ <value>1</value>
+ </param>
+ </block>
+ <block>
+ <key>blocks_vector_source_x</key>
+ <param>
+ <key>alias</key>
+ <value></value>
+ </param>
+ <param>
+ <key>comment</key>
+ <value></value>
+ </param>
+ <param>
+ <key>affinity</key>
+ <value></value>
+ </param>
+ <param>
+ <key>_enabled</key>
+ <value>True</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(208, 12)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>id</key>
+ <value>blocks_vector_source_x_0</value>
+ </param>
+ <param>
+ <key>maxoutbuf</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>minoutbuf</key>
+ <value>0</value>
+ </param>
+ <param>
+ <key>type</key>
+ <value>complex</value>
+ </param>
+ <param>
+ <key>repeat</key>
+ <value>False</value>
+ </param>
+ <param>
+ <key>tags</key>
+ <value>[]</value>
+ </param>
+ <param>
+ <key>vlen</key>
+ <value>1</value>
+ </param>
+ <param>
+ <key>vector</key>
+ <value>(0, 0, 0)</value>
+ </param>
+ </block>
+ <connection>
+ <source_block_id>blocks_add_const_vxx_0</source_block_id>
+ <sink_block_id>blocks_null_sink_0</sink_block_id>
+ <source_key>0</source_key>
+ <sink_key>0</sink_key>
+ </connection>
+ <connection>
+ <source_block_id>blocks_vector_source_x_0</source_block_id>
+ <sink_block_id>blocks_add_const_vxx_0</sink_block_id>
+ <source_key>0</source_key>
+ <sink_key>0</sink_key>
+ </connection>
+</flow_graph>
diff --git a/grc/tests/test_compiler.py b/grc/tests/test_compiler.py
new file mode 100644
index 0000000000..27b5670871
--- /dev/null
+++ b/grc/tests/test_compiler.py
@@ -0,0 +1,38 @@
+# Copyright 2016 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+from argparse import Namespace
+from os import path
+import tempfile
+
+from grc.compiler import main
+
+
+def test_compiler(capsys):
+ args = Namespace(
+ output=tempfile.gettempdir(),
+ user_lib_dir=False,
+ grc_files=[path.join(path.dirname(__file__), 'resources', 'test_compiler.grc')],
+ run=True
+ )
+
+ main(args)
+
+ out, err = capsys.readouterr()
+ assert not err