Enable GUI for GNURadio on macOS Mojave

本文将介绍如何在较新版本 macOS 中安装使用 GNURadio 以及开启可视化界面。

Without GUI

无 GUI 版本不会编译 gnuradio-compaion 文件。安装步骤很简单,如果你还没有安装 homebrew 请先按照其官网步骤安装,接下来只需执行:

1
brew install gnuradio

Enable GUI

由于官方停止了对 qt4 的支持,所以 homebrew 将 qt4 从其仓库中移除了。这导致依赖 qt4 的 GNURadio 关闭了 gui 的编译选项,这里我们要手动开启该选项,重新安装 qt4 以及其他依赖。qt4 的问题社区已经有解决方案了,即第三方仓库 cartr/qt4。接下来我们需要在自己的 homebrew 仓库中添加 wxpython@2 以及 gnuradio-gui

Install Dependencies

先添加第三方仓库并安装 qt4 相关依赖:

1
2
brew tap cartr/qt4
brew install qt@4 pyqt@4 qwt-qt4

Custom Formula

wxpython@2

我们需要修改原始的 wxpython.rb 配置,使其编译 python2 的库。修改后的 formula 如下,请把它放在自定义仓库中,文件名为 wxpython@2.rb

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
class WxpythonAT2 < Formula
include Language::Python::Virtualenv

desc "Python bindings for wxWidgets"
homepage "https://www.wxwidgets.org/"
url "https://files.pythonhosted.org/packages/9a/a1/9c081e04798eb134b63def3db121a6e4436e1d84e76692503deef8e75423/wxPython-4.0.6.tar.gz"
sha256 "35cc8ae9dd5246e2c9861bb796026bbcb9fb083e4d49650f776622171ecdab37"

depends_on "freetype"
depends_on "jpeg"
depends_on "libpng"
depends_on "libtiff"
depends_on "numpy"
depends_on "python@2"

keg_only :versioned_formula

resource "Pillow" do
url "https://files.pythonhosted.org/packages/81/1a/6b2971adc1bca55b9a53ed1efa372acff7e8b9913982a396f3fa046efaf8/Pillow-6.0.0.tar.gz"
sha256 "809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5"
end

resource "six" do
url "https://files.pythonhosted.org/packages/dd/bf/4138e7bfb757de47d1f4b6994648ec67a51efe58fa907c1e11e350cddfca/six-1.12.0.tar.gz"
sha256 "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
end

resource "scandir" do
url "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz"
sha256 "4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae"
end

resource "pathlib2" do
url "https://files.pythonhosted.org/packages/bf/d7/a2568f4596b75d2c6e2b4094a7e64f620decc7887f69a1f2811931ea15b9/pathlib2-2.3.3.tar.gz"
sha256 "25199318e8cc3c25dcb45cbe084cc061051336d5a9ea2a12448d3d8cb748f742"
end

# Fix build with recent macOS versions (remove with next version)
patch do
url "https://github.com/wxWidgets/Phoenix/commit/dcb06f6f.diff?full_index=1"
sha256 "efb0cf73be0f677967d98f8bb3af4885bd164882c2c930fb8abf23e5bdddc9fa"
end

# Do not require pathlib2 dependency (remove with next version)
patch do
url "https://github.com/wxWidgets/Phoenix/commit/2c4f9cb2.diff?full_index=1"
sha256 "1ec14aaf175ec64ccc00174c3bae416dc059c4c4138215bc4370377bfd15130b"
end

def install
# Fix build of included wxwidgets
# see https://github.com/wxWidgets/Phoenix/issues/1247
inreplace "buildtools/build_wxwidgets.py",
"#wxpy_configure_opts.append(\"--enable-monolithic\")",
"wxpy_configure_opts.append(\"--disable-precomp-headers\")"

venv = virtualenv_create(libexec)

resource("Pillow").stage do
inreplace "setup.py" do |s|
sdkprefix = MacOS.sdk_path_if_needed ? MacOS.sdk_path : ""
s.gsub! "openjpeg.h", "probably_not_a_header_called_this_eh.h"
s.gsub! "ZLIB_ROOT = None", "ZLIB_ROOT = ('#{sdkprefix}/usr/lib', '#{sdkprefix}/usr/include')"
s.gsub! "JPEG_ROOT = None", "JPEG_ROOT = ('#{Formula["jpeg"].opt_prefix}/lib', '#{Formula["jpeg"].opt_prefix}/include')"
s.gsub! "FREETYPE_ROOT = None", "FREETYPE_ROOT = ('#{Formula["freetype"].opt_prefix}/lib', '#{Formula["freetype"].opt_prefix}/include')"
end

# avoid triggering "helpful" distutils code that doesn't recognize Xcode 7 .tbd stubs
ENV.append "CFLAGS", "-I#{MacOS.sdk_path}/System/Library/Frameworks/Tk.framework/Versions/8.5/Headers" unless MacOS::CLT.installed?
venv.pip_install Pathname.pwd
end

res = resources.map(&:name).to_set - ["Pillow"]
res.each do |r|
venv.pip_install resource(r)
end

venv.pip_install_and_link buildpath
end

test do
xy = Language::Python.major_minor_version "python"
ENV.prepend_path "PYTHONPATH", libexec/"lib/python#{xy}/site-packages"

output = shell_output("python -c 'import wx ; print(wx.__version__)'")
assert_match version.to_s, output
end
end

gnuradio-gui

同样,我们需要修改原始的 gnuradio.rb 文件。修改后的 gnuradio-gui.rb 文件如下,请把它放在自定义仓库中:

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
class GnuradioGui < Formula
desc "SDK providing the signal processing runtime and processing blocks"
homepage "https://gnuradio.org/"
url "https://gnuradio.org/releases/gnuradio/gnuradio-3.7.13.4.tar.gz"
sha256 "c536c268b1e9c24f1206bbc881a5819ac46e662f4e8beaded6f3f441d3502f0d"
revision 7
head "https://github.com/gnuradio/gnuradio.git"

depends_on "cmake" => :build
depends_on "doxygen" => :build
depends_on "pkg-config" => :build
depends_on "sphinx-doc" => :build
depends_on "swig" => :build
depends_on "boost"
depends_on "fftw"
depends_on "gsl"
depends_on "numpy"
depends_on "portaudio"
depends_on "python@2"
depends_on "wxpython@2"
depends_on "uhd"
depends_on "zeromq"
depends_on "qt@4"
depends_on "pyqt@4"
depends_on "qwt-qt4"
depends_on "pygtk"

# cheetah starts here
resource "Markdown" do
url "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz"
sha256 "a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81"
end

resource "Cheetah" do
url "https://files.pythonhosted.org/packages/cd/b0/c2d700252fc251e91c08639ff41a8a5203b627f4e0a2ae18a6b662ab32ea/Cheetah-2.4.4.tar.gz"
sha256 "be308229f0c1e5e5af4f27d7ee06d90bb19e6af3059794e5fd536a6f29a9b550"
end
# cheetah ends here

resource "lxml" do
url "https://files.pythonhosted.org/packages/54/a6/43be8cf1cc23e3fa208cab04ba2f9c3b7af0233aab32af6b5089122b44cd/lxml-4.2.3.tar.gz"
sha256 "622f7e40faef13d232fb52003661f2764ce6cdef3edb0a59af7c1559e4cc36d1"
end

resource "MarkupSafe" do
url "https://files.pythonhosted.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz"
sha256 "a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
end

resource "Mako" do
url "https://files.pythonhosted.org/packages/eb/f3/67579bb486517c0d49547f9697e36582cd19dafb5df9e687ed8e22de57fa/Mako-1.0.7.tar.gz"
sha256 "4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae"
end

resource "six" do
url "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz"
sha256 "70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
end

resource "cppzmq" do
url "https://raw.githubusercontent.com/zeromq/cppzmq/46fc0572c5e9f09a32a23d6f22fd79b841f77e00/zmq.hpp"
sha256 "964031c0944f913933f55ad1610938105a6657a69d1ac5a6dd50e16a679104d5"
end

# patch for boost 1.70.0, remove after next release
patch do
url "https://github.com/gnuradio/gnuradio/commit/6dc8229fd0dda25c054c2194ee2c9b28affe92d8.patch?full_index=1"
sha256 "9836235ea69b3d66b5cd4b2cdc89f80d010797d2bd59dc5c6631a96af921db8c"
end

def install
ENV.prepend_path "PATH", "#{Formula["python@2"].opt_frameworks}/Python.framework/Versions/2.7/bin"

ENV["CHEETAH_INSTALL_WITHOUT_SETUPTOOLS"] = "1"
ENV["XML_CATALOG_FILES"] = etc/"xml/catalog"
ENV.prepend_create_path "PYTHONPATH", libexec/"vendor/lib/python2.7/site-packages"
ENV.prepend_path "PYTHONPATH", "#{Formula["wxpython@2"].opt_prefix}/libexec/lib/python2.7/site-packages"

%w[Markdown Cheetah MarkupSafe Mako six].each do |r|
resource(r).stage do
system "python", *Language::Python.setup_install_args(libexec/"vendor")
end
end

begin
# Fix "ld: file not found: /usr/lib/system/libsystem_darwin.dylib" for lxml
ENV["SDKROOT"] = MacOS.sdk_path if MacOS.version == :sierra

resource("lxml").stage do
system "python", *Language::Python.setup_install_args(libexec/"vendor")
end
ensure
ENV.delete("SDKROOT")
end

resource("cppzmq").stage include.to_s

args = std_cmake_args + %W[
-DGR_PKG_CONF_DIR=#{etc}/gnuradio/conf.d
-DGR_PREFSDIR=#{etc}/gnuradio/conf.d
-DPYTHON_LIBRARY=#{Formula["python@2"].opt_frameworks}/Python.framework/Versions/2.7/lib/libpython2.7.dylib
-DENABLE_DEFAULT=OFF
]

enabled = %w[GR_ANALOG GR_FFT VOLK GR_FILTER GNURADIO_RUNTIME
GR_BLOCKS GR_PAGER GR_NOAA GR_CHANNELS GR_AUDIO
GR_FCD GR_VOCODER GR_FEC GR_DIGITAL GR_DTV GR_ATSC
GR_TRELLIS GR_ZEROMQ GR_WAVELET GR_UHD DOXYGEN SPHINX
PYTHON GR_UTILS GR_CTRLPORT GRC GR_QTGUI GR_WXGUI]
enabled.each do |c|
args << "-DENABLE_#{c}=ON"
end

mkdir "build" do
system "cmake", "..", *args
system "make"
system "make", "install"
end

rm bin.children.reject(&:executable?)
bin.env_script_all_files(libexec/"bin", :PYTHONPATH => ENV["PYTHONPATH"])
end

test do
assert_match version.to_s, shell_output("#{bin}/gnuradio-config-info -v")

(testpath/"test.c++").write <<~EOS
#include <gnuradio/top_block.h>
#include <gnuradio/blocks/null_source.h>
#include <gnuradio/blocks/null_sink.h>
#include <gnuradio/blocks/head.h>
#include <gnuradio/gr_complex.h>

class top_block : public gr::top_block {
public:
top_block();
private:
gr::blocks::null_source::sptr null_source;
gr::blocks::null_sink::sptr null_sink;
gr::blocks::head::sptr head;
};

top_block::top_block() : gr::top_block("Top block") {
long s = sizeof(gr_complex);
null_source = gr::blocks::null_source::make(s);
null_sink = gr::blocks::null_sink::make(s);
head = gr::blocks::head::make(s, 1024);
connect(null_source, 0, head, 0);
connect(head, 0, null_sink, 0);
}

int main(int argc, char **argv) {
top_block top;
top.run();
}
EOS
system ENV.cxx, "-L#{lib}", "-L#{Formula["boost"].opt_lib}",
"-lgnuradio-blocks", "-lgnuradio-runtime", "-lgnuradio-pmt",
"-lboost_system", testpath/"test.c++", "-o", testpath/"test"
system "./test"

(testpath/"test.py").write <<~EOS
from gnuradio import blocks
from gnuradio import gr

class top_block(gr.top_block):
def __init__(self):
gr.top_block.__init__(self, "Top Block")
self.samp_rate = 32000
s = gr.sizeof_gr_complex
self.blocks_null_source_0 = blocks.null_source(s)
self.blocks_null_sink_0 = blocks.null_sink(s)
self.blocks_head_0 = blocks.head(s, 1024)
self.connect((self.blocks_head_0, 0),
(self.blocks_null_sink_0, 0))
self.connect((self.blocks_null_source_0, 0),
(self.blocks_head_0, 0))

def main(top_block_cls=top_block, options=None):
tb = top_block_cls()
tb.start()
tb.wait()

main()
EOS
system "python2.7", testpath/"test.py"

cd testpath do
system "#{bin}/gr_modtool", "newmod", "test"

cd "gr-test" do
system "#{bin}/gr_modtool", "add", "-t", "general", "test_ff", "-l",
"python", "-y", "--argument-list=''", "--add-python-qa",
"--copyright=brew"
end
end
end
end

Install GNURadio with GUI

现在执行下述命令即可安装开启了 GUI 版本的 GNURadio:

1
brew install gnuradio-gui

由于我们自定义的 formula 只能从源码安装,请做好 cpu 满载 30 分钟的准备。

如果你不想 qt4 相关的库污染系统环境,请在 gnuradio-gui 安装完成后执行:

1
brew unlink qt@4 pyqt@4 qwt-qt4

不知道 GNURadio 官方什么时候迁移到 qt5。