diff options
author | Martin Braun | 2013-01-24 19:33:03 +0100 |
---|---|---|
committer | Martin Braun | 2013-01-24 19:33:03 +0100 |
commit | a62f90d8cc96b9dea9289ad6e420d1c0b16f6c36 (patch) | |
tree | 963f16e442ad47a0cca5187d03f372edfa305dd5 /gr-utils | |
parent | 4435082f6167f0b5b519deaa1609572d596757dc (diff) | |
download | gnuradio-a62f90d8cc96b9dea9289ad6e420d1c0b16f6c36.tar.gz gnuradio-a62f90d8cc96b9dea9289ad6e420d1c0b16f6c36.tar.bz2 gnuradio-a62f90d8cc96b9dea9289ad6e420d1c0b16f6c36.zip |
utils: added modtool
Diffstat (limited to 'gr-utils')
21 files changed, 3600 insertions, 70 deletions
diff --git a/gr-utils/src/python/CMakeLists.txt b/gr-utils/src/python/CMakeLists.txt index d2af6b952..ef4e66d13 100644 --- a/gr-utils/src/python/CMakeLists.txt +++ b/gr-utils/src/python/CMakeLists.txt @@ -35,7 +35,7 @@ GR_PYTHON_INSTALL( GR_PYTHON_INSTALL( PROGRAMS - create-gnuradio-out-of-tree-project + gr_modtool gr_plot_char gr_plot_const gr_plot_fft @@ -55,3 +55,5 @@ GR_PYTHON_INSTALL( DESTINATION ${GR_RUNTIME_DIR} COMPONENT "utils" ) + +add_subdirectory(modtool) diff --git a/gr-utils/src/python/create-gnuradio-out-of-tree-project b/gr-utils/src/python/create-gnuradio-out-of-tree-project deleted file mode 100755 index d5e32c92b..000000000 --- a/gr-utils/src/python/create-gnuradio-out-of-tree-project +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python - -from optparse import OptionParser -import os -import re -import sys -import subprocess - -tarball_url="http://gnuradio.org/releases/gnuradio/gr-howto-write-a-block-3.3git-651-g642252d8.tar.gz" - -def open_tarball(tarball_name=None): - """Return a readable fileobject that references the tarball. - If tarball_name is None, download from the net - """ - if tarball_name: - return open(tarball_name, 'r') - p = subprocess.Popen(["wget", "-O", "-", tarball_url], - close_fds=True, stdout=subprocess.PIPE) - return p.stdout - -def extract_and_rename_files(tarball, module_name): - # Requires GNU tar. - tar_cmd = 'tar xz --strip-components=1 --transform="s/howto/%s/g"' % (module_name,) - p = subprocess.Popen(tar_cmd, shell=True, stdin=tarball, close_fds=True) - sts = os.waitpid(p.pid, 0) - return sts == 0 - -def main(): - usage = "%prog: [options] new_module_name" - description="Create a prototype 'out of tree' GNU Radio project" - parser = OptionParser(usage=usage, description=description) - parser.add_option("-T", "--tarball", type="string", default=None, - help="path to gr-howto-build-a-block gzipped tarball [default=<get from web>]") - (options, args) = parser.parse_args() - if len(args) != 1: - parser.print_help() - raise SystemExit,2 - module_name = args[0] - if not re.match(r'[_a-z][_a-z0-9]*$', module_name): - sys.stderr.write("module_name '%s' may only contain [a-z0-9_]\n" % (module_name)) - raise SystemExit, 1 - - # Ensure there's no file or directory called <module_name> - if os.path.exists(module_name): - sys.stderr.write("A file or directory named '%s' already exists\n" % (module_name,)) - raise SystemExit, 1 - - os.mkdir(module_name) - os.chdir(module_name) - - ok = extract_and_rename_files(open_tarball(options.tarball), module_name) - - # rename file contents - upper_module_name = module_name.upper() - sed_cmd = 'sed -i -e "s/howto-write-a-block/%s/g" -e "s/howto/%s/g" -e "s/HOWTO/%s/g"' % (module_name, module_name, \ - upper_module_name) - os.system('find . -type f -print0 | xargs -0 ' + sed_cmd) - - sys.stdout.write(""" -Project '%s' is now ready to build. - - $ ./bootstrap - $ ./configure --prefix=/home/[user]/install - $ (make && make check && make install) 2>&1 | tee make.log - -""" % (module_name,)) - -if __name__ == '__main__': - main() diff --git a/gr-utils/src/python/gr_modtool b/gr-utils/src/python/gr_modtool new file mode 100755 index 000000000..bc41d56f5 --- /dev/null +++ b/gr-utils/src/python/gr_modtool @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# +# Copyright 2012 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. +# +""" A tool for editing GNU Radio out-of-tree modules. """ + +import sys +from gnuradio.modtool import * + +def get_class_dict(): + " Return a dictionary of the available commands in the form command->class " + classdict = {} + for g in globals().values(): + try: + if issubclass(g, ModTool): + classdict[g.name] = g + for a in g.aliases: + classdict[a] = g + except (TypeError, AttributeError): + pass + return classdict + + +### Main code ################################################################ +def main(): + """ Here we go. Parse command, choose class and run. """ + cmd_dict = get_class_dict() + command = get_command_from_argv(cmd_dict.keys()) + if command is None: + print 'Usage:' + templates.Templates['usage'] + exit(2) + modtool = cmd_dict[command]() + modtool.setup() + modtool.run() + +if __name__ == '__main__': + if not ((sys.version_info[0] > 2) or + (sys.version_info[0] == 2 and sys.version_info[1] >= 7)): + print "Using Python < 2.7 is not recommended for gr_modtool." + try: + main() + except KeyboardInterrupt: + pass + diff --git a/gr-utils/src/python/modtool/CMakeLists.txt b/gr-utils/src/python/modtool/CMakeLists.txt new file mode 100644 index 000000000..3425b50d0 --- /dev/null +++ b/gr-utils/src/python/modtool/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright 2011 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. + +GR_PYTHON_INSTALL(FILES + __init__.py + cmakefile_editor.py + code_generator.py + grc_xml_generator.py + modtool_add.py + modtool_base.py + modtool_disable.py + modtool_help.py + modtool_info.py + modtool_makexml.py + modtool_newmod.py + modtool_rm.py + newmod_tarfile.py + parser_cc_block.py + templates.py + util_functions.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool + COMPONENT "utils" +) + diff --git a/gr-utils/src/python/modtool/README.modtool b/gr-utils/src/python/modtool/README.modtool new file mode 100644 index 000000000..93ce97292 --- /dev/null +++ b/gr-utils/src/python/modtool/README.modtool @@ -0,0 +1,8 @@ +gr_modtool: Swiss Army Knife for editing GNU Radio modules and -components. + +Adding a new subcommand +======================= + +* Add a new file called modtool_SUBCOMMAND +* Have a look at the other subcommands, it must inherit from ModTool +* Add that file to __init__.py and CMakeLists.txt diff --git a/gr-utils/src/python/modtool/__init__.py b/gr-utils/src/python/modtool/__init__.py new file mode 100644 index 000000000..a10747254 --- /dev/null +++ b/gr-utils/src/python/modtool/__init__.py @@ -0,0 +1,34 @@ +# +# Copyright 2012 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 cmakefile_editor import CMakeFileEditor +from code_generator import GRMTemplate +from grc_xml_generator import GRCXMLGenerator +from modtool_add import ModToolAdd +from modtool_base import ModTool +from modtool_disable import ModToolDisable +from modtool_help import ModToolHelp +from modtool_info import ModToolInfo +from modtool_makexml import ModToolMakeXML +from modtool_newmod import ModToolNewModule +from modtool_rm import ModToolRemove +from parser_cc_block import ParserCCBlock +from util_functions import * diff --git a/gr-utils/src/python/modtool/cmakefile_editor.py b/gr-utils/src/python/modtool/cmakefile_editor.py new file mode 100644 index 000000000..fe50373bb --- /dev/null +++ b/gr-utils/src/python/modtool/cmakefile_editor.py @@ -0,0 +1,97 @@ +""" Edit CMakeLists.txt files """ + +import re + +### CMakeFile.txt editor class ############################################### +class CMakeFileEditor(object): + """A tool for editing CMakeLists.txt files. """ + def __init__(self, filename, separator=' ', indent=' '): + self.filename = filename + self.cfile = open(filename, 'r').read() + self.separator = separator + self.indent = indent + + def get_entry_value(self, entry, to_ignore=''): + """ Get the value of an entry. + to_ignore is the part of the entry you don't care about. """ + regexp = '%s\(%s([^()]+)\)' % (entry, to_ignore) + mobj = re.search(regexp, self.cfile, flags=re.MULTILINE) + if mobj is None: + return None + value = mobj.groups()[0].strip() + return value + + def append_value(self, entry, value, to_ignore=''): + """ Add a value to an entry. """ + regexp = re.compile('(%s\([^()]*?)\s*?(\s?%s)\)' % (entry, to_ignore), + re.MULTILINE) + substi = r'\1' + self.separator + value + r'\2)' + self.cfile = regexp.sub(substi, self.cfile, count=1) + + def remove_value(self, entry, value, to_ignore=''): + """Remove a value from an entry.""" + regexp = '^\s*(%s\(\s*%s[^()]*?\s*)%s\s*([^()]*\))' % (entry, to_ignore, value) + regexp = re.compile(regexp, re.MULTILINE) + self.cfile = re.sub(regexp, r'\1\2', self.cfile, count=1) + + def delete_entry(self, entry, value_pattern=''): + """Remove an entry from the current buffer.""" + regexp = '%s\s*\([^()]*%s[^()]*\)[^\n]*\n' % (entry, value_pattern) + regexp = re.compile(regexp, re.MULTILINE) + self.cfile = re.sub(regexp, '', self.cfile, count=1) + + def write(self): + """ Write the changes back to the file. """ + open(self.filename, 'w').write(self.cfile) + + def remove_double_newlines(self): + """Simply clear double newlines from the file buffer.""" + self.cfile = re.compile('\n\n\n+', re.MULTILINE).sub('\n\n', self.cfile) + + def find_filenames_match(self, regex): + """ Find the filenames that match a certain regex + on lines that aren't comments """ + filenames = [] + reg = re.compile(regex) + fname_re = re.compile('[a-zA-Z]\w+\.\w{1,5}$') + for line in self.cfile.splitlines(): + if len(line.strip()) == 0 or line.strip()[0] == '#': + continue + for word in re.split('[ /)(\t\n\r\f\v]', line): + if fname_re.match(word) and reg.search(word): + filenames.append(word) + return filenames + + def disable_file(self, fname): + """ Comment out a file """ + starts_line = False + for line in self.cfile.splitlines(): + if len(line.strip()) == 0 or line.strip()[0] == '#': + continue + if re.search(r'\b'+fname+r'\b', line): + if re.match(fname, line.lstrip()): + starts_line = True + break + comment_out_re = r'#\1' + '\n' + self.indent + if not starts_line: + comment_out_re = r'\n' + self.indent + comment_out_re + (self.cfile, nsubs) = re.subn(r'(\b'+fname+r'\b)\s*', comment_out_re, self.cfile) + if nsubs == 0: + print "Warning: A replacement failed when commenting out %s. Check the CMakeFile.txt manually." % fname + elif nsubs > 1: + print "Warning: Replaced %s %d times (instead of once). Check the CMakeFile.txt manually." % (fname, nsubs) + + def comment_out_lines(self, pattern, comment_str='#'): + """ Comments out all lines that match with pattern """ + for line in self.cfile.splitlines(): + if re.search(pattern, line): + self.cfile = self.cfile.replace(line, comment_str+line) + + def check_for_glob(self, globstr): + """ Returns true if a glob as in globstr is found in the cmake file """ + glob_re = r'GLOB\s[a-z_]+\s"%s"' % globstr.replace('*', '\*') + if re.search(glob_re, self.cfile, flags=re.MULTILINE|re.IGNORECASE) is not None: + return True + else: + return False + diff --git a/gr-utils/src/python/modtool/code_generator.py b/gr-utils/src/python/modtool/code_generator.py new file mode 100644 index 000000000..b727f611e --- /dev/null +++ b/gr-utils/src/python/modtool/code_generator.py @@ -0,0 +1,32 @@ +""" A code generator (needed by ModToolAdd) """ + +from templates import Templates +import Cheetah.Template +from util_functions import str_to_fancyc_comment +from util_functions import str_to_python_comment +from util_functions import strip_default_values +from util_functions import strip_arg_types + +### Code generator class ##################################################### +class GRMTemplate(Cheetah.Template.Template): + """ An extended template class """ + def __init__(self, src, searchList): + self.grtypelist = { + 'sync': 'gr_sync_block', + 'sink': 'gr_sync_block', + 'source': 'gr_sync_block', + 'decimator': 'gr_sync_decimator', + 'interpolator': 'gr_sync_interpolator', + 'general': 'gr_block', + 'hier': 'gr_hier_block2', + 'noblock': ''} + searchList['str_to_fancyc_comment'] = str_to_fancyc_comment + searchList['str_to_python_comment'] = str_to_python_comment + searchList['strip_default_values'] = strip_default_values + searchList['strip_arg_types'] = strip_arg_types + Cheetah.Template.Template.__init__(self, src, searchList=searchList) + self.grblocktype = self.grtypelist[searchList['blocktype']] + +def get_template(tpl_id, **kwargs): + """ Return the template given by tpl_id, parsed through Cheetah """ + return str(GRMTemplate(Templates[tpl_id], searchList=kwargs)) diff --git a/gr-utils/src/python/modtool/grc_xml_generator.py b/gr-utils/src/python/modtool/grc_xml_generator.py new file mode 100644 index 000000000..2fa61863f --- /dev/null +++ b/gr-utils/src/python/modtool/grc_xml_generator.py @@ -0,0 +1,85 @@ +import xml.etree.ElementTree as ET +from util_functions import is_number, xml_indent + +### GRC XML Generator ######################################################## +try: + import lxml.etree + LXML_IMPORTED = True +except ImportError: + LXML_IMPORTED = False + +class GRCXMLGenerator(object): + """ Create and write the XML bindings for a GRC block. """ + def __init__(self, modname=None, blockname=None, doc=None, params=None, iosig=None): + """docstring for __init__""" + params_list = ['$'+s['key'] for s in params if s['in_constructor']] + # Can't make a dict 'cause order matters + self._header = (('name', blockname.replace('_', ' ').capitalize()), + ('key', '%s_%s' % (modname, blockname)), + ('category', modname.upper()), + ('import', 'import %s' % modname), + ('make', '%s.%s(%s)' % (modname, blockname, ', '.join(params_list))) + ) + self.params = params + self.iosig = iosig + self.doc = doc + self.root = None + if LXML_IMPORTED: + self._prettyprint = self._lxml_prettyprint + else: + self._prettyprint = self._manual_prettyprint + + def _lxml_prettyprint(self): + """ XML pretty printer using lxml """ + return lxml.etree.tostring( + lxml.etree.fromstring(ET.tostring(self.root, encoding="UTF-8")), + pretty_print=True + ) + + def _manual_prettyprint(self): + """ XML pretty printer using xml_indent """ + xml_indent(self.root) + return ET.tostring(self.root, encoding="UTF-8") + + def make_xml(self): + """ Create the actual tag tree """ + root = ET.Element("block") + iosig = self.iosig + for tag, value in self._header: + this_tag = ET.SubElement(root, tag) + this_tag.text = value + for param in self.params: + param_tag = ET.SubElement(root, 'param') + ET.SubElement(param_tag, 'name').text = param['key'].capitalize() + ET.SubElement(param_tag, 'key').text = param['key'] + if len(param['default']): + ET.SubElement(param_tag, 'value').text = param['default'] + ET.SubElement(param_tag, 'type').text = param['type'] + for inout in sorted(iosig.keys()): + if iosig[inout]['max_ports'] == '0': + continue + for i in range(len(iosig[inout]['type'])): + s_tag = ET.SubElement(root, {'in': 'sink', 'out': 'source'}[inout]) + ET.SubElement(s_tag, 'name').text = inout + ET.SubElement(s_tag, 'type').text = iosig[inout]['type'][i] + if iosig[inout]['vlen'][i] != '1': + vlen = iosig[inout]['vlen'][i] + if is_number(vlen): + ET.SubElement(s_tag, 'vlen').text = vlen + else: + ET.SubElement(s_tag, 'vlen').text = '$'+vlen + if i == len(iosig[inout]['type'])-1: + if not is_number(iosig[inout]['max_ports']): + ET.SubElement(s_tag, 'nports').text = iosig[inout]['max_ports'] + elif len(iosig[inout]['type']) < int(iosig[inout]['max_ports']): + ET.SubElement(s_tag, 'nports').text = str(int(iosig[inout]['max_ports']) - + len(iosig[inout]['type'])+1) + if self.doc is not None: + ET.SubElement(root, 'doc').text = self.doc + self.root = root + + def save(self, filename): + """ Write the XML file """ + self.make_xml() + open(filename, 'w').write(self._prettyprint()) + diff --git a/gr-utils/src/python/modtool/modtool_add.py b/gr-utils/src/python/modtool/modtool_add.py new file mode 100644 index 000000000..c664d7c1a --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_add.py @@ -0,0 +1,296 @@ +""" Module to add new blocks """ + +import os +import sys +import re +from optparse import OptionGroup + +from util_functions import append_re_line_sequence, ask_yes_no +from cmakefile_editor import CMakeFileEditor +from modtool_base import ModTool +from templates import Templates +from code_generator import get_template +import Cheetah.Template + +### Add new block module ##################################################### +class ModToolAdd(ModTool): + """ Add block to the out-of-tree module. """ + name = 'add' + aliases = ('insert',) + _block_types = ('sink', 'source', 'sync', 'decimator', 'interpolator', + 'general', 'hier', 'noblock') + def __init__(self): + ModTool.__init__(self) + self._add_cc_qa = False + self._add_py_qa = False + + def setup_parser(self): + parser = ModTool.setup_parser(self) + parser.usage = '%prog add [options]. \n Call %prog without any options to run it interactively.' + ogroup = OptionGroup(parser, "Add module options") + ogroup.add_option("-t", "--block-type", type="choice", + choices=self._block_types, default=None, help="One of %s." % ', '.join(self._block_types)) + ogroup.add_option("--license-file", type="string", default=None, + help="File containing the license header for every source code file.") + ogroup.add_option("--argument-list", type="string", default=None, + help="The argument list for the constructor and make functions.") + ogroup.add_option("--add-python-qa", action="store_true", default=None, + help="If given, Python QA code is automatically added if possible.") + ogroup.add_option("--add-cpp-qa", action="store_true", default=None, + help="If given, C++ QA code is automatically added if possible.") + ogroup.add_option("--skip-cmakefiles", action="store_true", default=False, + help="If given, only source files are written, but CMakeLists.txt files are left unchanged.") + ogroup.add_option("-l", "--lang", type="choice", choices=('cpp', 'c++', 'python'), + default='cpp', help="Language (cpp or python)") + parser.add_option_group(ogroup) + return parser + + def setup(self): + ModTool.setup(self) + options = self.options + self._info['blocktype'] = options.block_type + if self._info['blocktype'] is None: + while self._info['blocktype'] not in self._block_types: + self._info['blocktype'] = raw_input("Enter code type: ") + if self._info['blocktype'] not in self._block_types: + print 'Must be one of ' + str(self._block_types) + self._info['lang'] = options.lang + if self._info['lang'] == 'c++': + self._info['lang'] = 'cpp' + print "Language: %s" % {'cpp': 'C++', 'python': 'Python'}[self._info['lang']] + + if ((self._skip_subdirs['lib'] and self._info['lang'] == 'cpp') + or (self._skip_subdirs['python'] and self._info['lang'] == 'python')): + print "Missing or skipping relevant subdir." + sys.exit(1) + + if self._info['blockname'] is None: + if len(self.args) >= 2: + self._info['blockname'] = self.args[1] + else: + self._info['blockname'] = raw_input("Enter name of block/code (without module name prefix): ") + if not re.match('[a-zA-Z0-9_]+', self._info['blockname']): + print 'Invalid block name.' + sys.exit(2) + print "Block/code identifier: " + self._info['blockname'] + self._info['fullblockname'] = self._info['modname'] + '_' + self._info['blockname'] + self._info['license'] = self.setup_choose_license() + + if options.argument_list is not None: + self._info['arglist'] = options.argument_list + else: + self._info['arglist'] = raw_input('Enter valid argument list, including default arguments: ') + + if not (self._info['blocktype'] in ('noblock') or self._skip_subdirs['python']): + self._add_py_qa = options.add_python_qa + if self._add_py_qa is None: + self._add_py_qa = ask_yes_no('Add Python QA code?', True) + if self._info['lang'] == 'cpp': + self._add_cc_qa = options.add_cpp_qa + if self._add_cc_qa is None: + self._add_cc_qa = ask_yes_no('Add C++ QA code?', not self._add_py_qa) + if self._info['version'] == 'autofoo' and not self.options.skip_cmakefiles: + print "Warning: Autotools modules are not supported. ", + print "Files will be created, but Makefiles will not be edited." + self.options.skip_cmakefiles = True + + + def setup_choose_license(self): + """ Select a license by the following rules, in this order: + 1) The contents of the file given by --license-file + 2) The contents of the file LICENSE or LICENCE in the modules + top directory + 3) The default license. """ + if self.options.license_file is not None \ + and os.path.isfile(self.options.license_file): + return open(self.options.license_file).read() + elif os.path.isfile('LICENSE'): + return open('LICENSE').read() + elif os.path.isfile('LICENCE'): + return open('LICENCE').read() + else: + return Templates['defaultlicense'] + + def _write_tpl(self, tpl, path, fname): + """ Shorthand for writing a substituted template to a file""" + print "Adding file '%s'..." % fname + open(os.path.join(path, fname), 'w').write(get_template(tpl, **self._info)) + + def run(self): + """ Go, go, go. """ + has_swig = ( + self._info['lang'] == 'cpp' + and not self._skip_subdirs['swig'] + ) + has_grc = False + if self._info['lang'] == 'cpp': + print "Traversing lib..." + self._run_lib() + has_grc = has_swig + else: # Python + print "Traversing python..." + self._run_python() + if self._info['blocktype'] != 'noblock': + has_grc = True + if has_swig: + print "Traversing swig..." + self._run_swig() + if self._add_py_qa: + print "Adding Python QA..." + self._run_python_qa() + if has_grc and not self._skip_subdirs['grc']: + print "Traversing grc..." + self._run_grc() + + def _run_lib(self): + """ Do everything that needs doing in the subdir 'lib' and 'include'. + - add .cc and .h files + - include them into CMakeLists.txt + - check if C++ QA code is req'd + - if yes, create qa_*.{cc,h} and add them to CMakeLists.txt + """ + def _add_qa(): + " Add C++ QA files for 3.7 API " + fname_qa_h = 'qa_%s.h' % self._info['blockname'] + fname_qa_cc = 'qa_%s.cc' % self._info['blockname'] + self._write_tpl('qa_cpp', 'lib', fname_qa_cc) + self._write_tpl('qa_h', 'lib', fname_qa_h) + if not self.options.skip_cmakefiles: + try: + append_re_line_sequence(self._file['cmlib'], + '\$\{CMAKE_CURRENT_SOURCE_DIR\}/qa_%s.cc.*\n' % self._info['modname'], + ' ${CMAKE_CURRENT_SOURCE_DIR}/qa_%s.cc' % self._info['blockname']) + append_re_line_sequence(self._file['qalib'], + '#include.*\n', + '#include "%s"' % fname_qa_h) + append_re_line_sequence(self._file['qalib'], + '(addTest.*suite.*\n|new CppUnit.*TestSuite.*\n)', + ' s->addTest(gr::%s::qa_%s::suite());' % (self._info['modname'], + self._info['blockname']) + ) + except IOError: + print "Can't add C++ QA files." + def _add_qa36(): + " Add C++ QA files for pre-3.7 API (not autotools) " + fname_qa_cc = 'qa_%s.cc' % self._info['fullblockname'] + self._write_tpl('qa_cpp36', 'lib', fname_qa_cc) + if not self.options.skip_cmakefiles: + open(self._file['cmlib'], 'a').write( + str( + Cheetah.Template.Template( + Templates['qa_cmakeentry36'], + searchList={'basename': os.path.splitext(fname_qa_cc)[0], + 'filename': fname_qa_cc, + 'modname': self._info['modname'] + } + ) + ) + ) + ed = CMakeFileEditor(self._file['cmlib']) + ed.remove_double_newlines() + ed.write() + fname_cc = None + fname_h = None + if self._info['version'] == '37': + fname_h = self._info['blockname'] + '.h' + fname_cc = self._info['blockname'] + '.cc' + if self._info['blocktype'] in ('source', 'sink', 'sync', 'decimator', + 'interpolator', 'general', 'hier'): + fname_cc = self._info['blockname'] + '_impl.cc' + self._write_tpl('block_impl_h', 'lib', self._info['blockname'] + '_impl.h') + self._write_tpl('block_impl_cpp', 'lib', fname_cc) + self._write_tpl('block_def_h', self._info['includedir'], fname_h) + else: # Pre-3.7 or autotools + fname_h = self._info['fullblockname'] + '.h' + fname_cc = self._info['fullblockname'] + '.cc' + self._write_tpl('block_h36', self._info['includedir'], fname_h) + self._write_tpl('block_cpp36', 'lib', fname_cc) + if not self.options.skip_cmakefiles: + ed = CMakeFileEditor(self._file['cmlib']) + ed.append_value('add_library', fname_cc) + ed.write() + ed = CMakeFileEditor(self._file['cminclude']) + ed.append_value('install', fname_h, 'DESTINATION[^()]+') + ed.write() + if self._add_cc_qa: + if self._info['version'] == '37': + _add_qa() + elif self._info['version'] == '36': + _add_qa36() + elif self._info['version'] == 'autofoo': + print "Warning: C++ QA files not supported for autotools." + + def _run_swig(self): + """ Do everything that needs doing in the subdir 'swig'. + - Edit main *.i file + """ + if self._get_mainswigfile() is None: + print 'Warning: No main swig file found.' + return + print "Editing %s..." % self._file['swig'] + mod_block_sep = '/' + if self._info['version'] == '36': + mod_block_sep = '_' + swig_block_magic_str = get_template('swig_block_magic', **self._info) + include_str = '#include "%s%s%s.h"' % ( + self._info['modname'], + mod_block_sep, + self._info['blockname']) + if re.search('#include', open(self._file['swig'], 'r').read()): + append_re_line_sequence(self._file['swig'], '^#include.*\n', include_str) + else: # I.e., if the swig file is empty + oldfile = open(self._file['swig'], 'r').read() + regexp = re.compile('^%\{\n', re.MULTILINE) + oldfile = regexp.sub('%%{\n%s\n' % include_str, oldfile, count=1) + open(self._file['swig'], 'w').write(oldfile) + open(self._file['swig'], 'a').write(swig_block_magic_str) + + def _run_python_qa(self): + """ Do everything that needs doing in the subdir 'python' to add + QA code. + - add .py files + - include in CMakeLists.txt + """ + fname_py_qa = 'qa_' + self._info['blockname'] + '.py' + self._write_tpl('qa_python', 'python', fname_py_qa) + os.chmod(os.path.join('python', fname_py_qa), 0755) + if self.options.skip_cmakefiles or CMakeFileEditor(self._file['cmpython']).check_for_glob('qa_*.py'): + return + print "Editing python/CMakeLists.txt..." + open(self._file['cmpython'], 'a').write( + 'GR_ADD_TEST(qa_%s ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/%s)\n' % \ + (self._info['blockname'], fname_py_qa)) + + def _run_python(self): + """ Do everything that needs doing in the subdir 'python' to add + a Python block. + - add .py file + - include in CMakeLists.txt + - include in __init__.py + """ + fname_py = self._info['blockname'] + '.py' + self._write_tpl('block_python', 'python', fname_py) + append_re_line_sequence(self._file['pyinit'], + '(^from.*import.*\n|# import any pure.*\n)', + 'from %s import *' % self._info['blockname']) + if self.options.skip_cmakefiles: + return + ed = CMakeFileEditor(self._file['cmpython']) + ed.append_value('GR_PYTHON_INSTALL', fname_py, 'DESTINATION[^()]+') + ed.write() + + def _run_grc(self): + """ Do everything that needs doing in the subdir 'grc' to add + a GRC bindings XML file. + - add .xml file + - include in CMakeLists.txt + """ + fname_grc = self._info['fullblockname'] + '.xml' + self._write_tpl('grc_xml', 'grc', fname_grc) + ed = CMakeFileEditor(self._file['cmgrc'], '\n ') + if self.options.skip_cmakefiles or ed.check_for_glob('*.xml'): + return + print "Editing grc/CMakeLists.txt..." + ed.append_value('install', fname_grc, 'DESTINATION[^()]+') + ed.write() + diff --git a/gr-utils/src/python/modtool/modtool_base.py b/gr-utils/src/python/modtool/modtool_base.py new file mode 100644 index 000000000..edb0f14ee --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_base.py @@ -0,0 +1,139 @@ +""" Base class for the modules """ + +import os +import re +import sys +from optparse import OptionParser, OptionGroup + +from util_functions import get_modname +from templates import Templates + +### ModTool base class ####################################################### +class ModTool(object): + """ Base class for all modtool command classes. """ + def __init__(self): + self._subdirs = ['lib', 'include', 'python', 'swig', 'grc'] # List subdirs where stuff happens + self._has_subdirs = {} + self._skip_subdirs = {} + self._info = {} + self._file = {} + for subdir in self._subdirs: + self._has_subdirs[subdir] = False + self._skip_subdirs[subdir] = False + self.parser = self.setup_parser() + self.args = None + self.options = None + self._dir = None + + def setup_parser(self): + """ Init the option parser. If derived classes need to add options, + override this and call the parent function. """ + parser = OptionParser(usage=Templates['usage'], add_help_option=False) + ogroup = OptionGroup(parser, "General options") + ogroup.add_option("-h", "--help", action="help", help="Displays this help message.") + ogroup.add_option("-d", "--directory", type="string", default=".", + help="Base directory of the module.") + ogroup.add_option("-n", "--module-name", type="string", default=None, + help="Name of the GNU Radio module. If possible, this gets detected from CMakeLists.txt.") + ogroup.add_option("-N", "--block-name", type="string", default=None, + help="Name of the block, minus the module name prefix.") + ogroup.add_option("--skip-lib", action="store_true", default=False, + help="Don't do anything in the lib/ subdirectory.") + ogroup.add_option("--skip-swig", action="store_true", default=False, + help="Don't do anything in the swig/ subdirectory.") + ogroup.add_option("--skip-python", action="store_true", default=False, + help="Don't do anything in the python/ subdirectory.") + ogroup.add_option("--skip-grc", action="store_true", default=False, + help="Don't do anything in the grc/ subdirectory.") + parser.add_option_group(ogroup) + return parser + + def setup(self): + """ Initialise all internal variables, such as the module name etc. """ + (options, self.args) = self.parser.parse_args() + self._dir = options.directory + if not self._check_directory(self._dir): + print "No GNU Radio module found in the given directory. Quitting." + sys.exit(1) + print "Operating in directory " + self._dir + if options.module_name is not None: + self._info['modname'] = options.module_name + else: + self._info['modname'] = get_modname() + if self._info['modname'] is None: + print "No GNU Radio module found in the given directory. Quitting." + sys.exit(1) + print "GNU Radio module name identified: " + self._info['modname'] + if self._info['version'] == '36' and os.path.isdir(os.path.join('include', self._info['modname'])): + self._info['version'] = '37' + if options.skip_lib or not self._has_subdirs['lib']: + self._skip_subdirs['lib'] = True + if options.skip_python or not self._has_subdirs['python']: + self._skip_subdirs['python'] = True + if options.skip_swig or self._get_mainswigfile() is None or not self._has_subdirs['swig']: + self._skip_subdirs['swig'] = True + if options.skip_grc or not self._has_subdirs['grc']: + self._skip_subdirs['grc'] = True + self._info['blockname'] = options.block_name + self.options = options + self._setup_files() + + def _setup_files(self): + """ Initialise the self._file[] dictionary """ + if not self._skip_subdirs['swig']: + self._file['swig'] = os.path.join('swig', self._get_mainswigfile()) + self._file['qalib'] = os.path.join('lib', 'qa_%s.cc' % self._info['modname']) + self._file['pyinit'] = os.path.join('python', '__init__.py') + self._file['cmlib'] = os.path.join('lib', 'CMakeLists.txt') + self._file['cmgrc'] = os.path.join('grc', 'CMakeLists.txt') + self._file['cmpython'] = os.path.join('python', 'CMakeLists.txt') + if self._info['version'] in ('37', 'component'): + self._info['includedir'] = os.path.join('include', self._info['modname']) + else: + self._info['includedir'] = 'include' + self._file['cminclude'] = os.path.join(self._info['includedir'], 'CMakeLists.txt') + self._file['cmswig'] = os.path.join('swig', 'CMakeLists.txt') + + def _check_directory(self, directory): + """ Guesses if dir is a valid GNU Radio module directory by looking for + CMakeLists.txt and at least one of the subdirs lib/, python/ and swig/. + Changes the directory, if valid. """ + has_makefile = False + try: + files = os.listdir(directory) + os.chdir(directory) + except OSError: + print "Can't read or chdir to directory %s." % directory + return False + for f in files: + if os.path.isfile(f) and f == 'CMakeLists.txt': + if re.search('find_package\(GnuradioCore\)', open(f).read()) is not None: + self._info['version'] = '36' # Might be 37, check that later + has_makefile = True + elif re.search('GR_REGISTER_COMPONENT', open(f).read()) is not None: + self._info['version'] = '36' # Might be 37, check that later + self._info['is_component'] = True + has_makefile = True + # TODO search for autofoo + elif os.path.isdir(f): + if (f in self._has_subdirs.keys()): + self._has_subdirs[f] = True + else: + self._skip_subdirs[f] = True + return bool(has_makefile and (self._has_subdirs.values())) + + def _get_mainswigfile(self): + """ Find out which name the main SWIG file has. In particular, is it + a MODNAME.i or a MODNAME_swig.i? Returns None if none is found. """ + modname = self._info['modname'] + swig_files = (modname + '.i', + modname + '_swig.i') + for fname in swig_files: + if os.path.isfile(os.path.join(self._dir, 'swig', fname)): + return fname + return None + + def run(self): + """ Override this. """ + pass + diff --git a/gr-utils/src/python/modtool/modtool_disable.py b/gr-utils/src/python/modtool/modtool_disable.py new file mode 100644 index 000000000..67f15ad53 --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_disable.py @@ -0,0 +1,144 @@ +""" Disable blocks module """ + +import os +import re +import sys +from optparse import OptionGroup + +from modtool_base import ModTool +from cmakefile_editor import CMakeFileEditor + +### Disable module ########################################################### +class ModToolDisable(ModTool): + """ Disable block (comments out CMake entries for files) """ + name = 'disable' + aliases = ('dis',) + def __init__(self): + ModTool.__init__(self) + + def setup_parser(self): + " Initialise the option parser for 'gr_modtool.py rm' " + parser = ModTool.setup_parser(self) + parser.usage = '%prog disable [options]. \n Call %prog without any options to run it interactively.' + ogroup = OptionGroup(parser, "Disable module options") + ogroup.add_option("-p", "--pattern", type="string", default=None, + help="Filter possible choices for blocks to be disabled.") + ogroup.add_option("-y", "--yes", action="store_true", default=False, + help="Answer all questions with 'yes'.") + parser.add_option_group(ogroup) + return parser + + def setup(self): + ModTool.setup(self) + options = self.options + if options.pattern is not None: + self._info['pattern'] = options.pattern + elif options.block_name is not None: + self._info['pattern'] = options.block_name + elif len(self.args) >= 2: + self._info['pattern'] = self.args[1] + else: + self._info['pattern'] = raw_input('Which blocks do you want to disable? (Regex): ') + if len(self._info['pattern']) == 0: + self._info['pattern'] = '.' + self._info['yes'] = options.yes + + def run(self): + """ Go, go, go! """ + def _handle_py_qa(cmake, fname): + """ Do stuff for py qa """ + cmake.comment_out_lines('GR_ADD_TEST.*'+fname) + return True + def _handle_py_mod(cmake, fname): + """ Do stuff for py extra files """ + try: + initfile = open(self._file['pyinit']).read() + except IOError: + print "Could not edit __init__.py, that might be a problem." + return False + pymodname = os.path.splitext(fname)[0] + initfile = re.sub(r'((from|import)\s+\b'+pymodname+r'\b)', r'#\1', initfile) + open(self._file['pyinit'], 'w').write(initfile) + return False + def _handle_cc_qa(cmake, fname): + """ Do stuff for cc qa """ + if self._info['version'] == '37': + cmake.comment_out_lines('\$\{CMAKE_CURRENT_SOURCE_DIR\}/'+fname) + fname_base = os.path.splitext(fname)[0] + ed = CMakeFileEditor(self._file['qalib']) # Abusing the CMakeFileEditor... + ed.comment_out_lines('#include\s+"%s.h"' % fname_base, comment_str='//') + ed.comment_out_lines('%s::suite\(\)' % fname_base, comment_str='//') + ed.write() + elif self._info['version'] == '36': + cmake.comment_out_lines('add_executable.*'+fname) + cmake.comment_out_lines('target_link_libraries.*'+os.path.splitext(fname)[0]) + cmake.comment_out_lines('GR_ADD_TEST.*'+os.path.splitext(fname)[0]) + return True + def _handle_h_swig(cmake, fname): + """ Comment out include files from the SWIG file, + as well as the block magic """ + swigfile = open(self._file['swig']).read() + (swigfile, nsubs) = re.subn('(.include\s+"(%s/)?%s")' % ( + self._info['modname'], fname), + r'//\1', swigfile) + if nsubs > 0: + print "Changing %s..." % self._file['swig'] + if nsubs > 1: # Need to find a single BLOCK_MAGIC + blockname = os.path.splitext(fname[len(self._info['modname'])+1:])[0] + if self._info['version'] == '37': + blockname = os.path.splitext(fname)[0] + (swigfile, nsubs) = re.subn('(GR_SWIG_BLOCK_MAGIC2?.+%s.+;)' % blockname, r'//\1', swigfile) + if nsubs > 1: + print "Hm, changed more then expected while editing %s." % self._file['swig'] + open(self._file['swig'], 'w').write(swigfile) + return False + def _handle_i_swig(cmake, fname): + """ Comment out include files from the SWIG file, + as well as the block magic """ + swigfile = open(self._file['swig']).read() + blockname = os.path.splitext(fname[len(self._info['modname'])+1:])[0] + if self._info['version'] == '37': + blockname = os.path.splitext(fname)[0] + swigfile = re.sub('(%include\s+"'+fname+'")', r'//\1', swigfile) + print "Changing %s..." % self._file['swig'] + swigfile = re.sub('(GR_SWIG_BLOCK_MAGIC2?.+'+blockname+'.+;)', r'//\1', swigfile) + open(self._file['swig'], 'w').write(swigfile) + return False + # List of special rules: 0: subdir, 1: filename re match, 2: function + special_treatments = ( + ('python', 'qa.+py$', _handle_py_qa), + ('python', '^(?!qa).+py$', _handle_py_mod), + ('lib', 'qa.+\.cc$', _handle_cc_qa), + ('include/%s' % self._info['modname'], '.+\.h$', _handle_h_swig), + ('include', '.+\.h$', _handle_h_swig), + ('swig', '.+\.i$', _handle_i_swig) + ) + for subdir in self._subdirs: + if self._skip_subdirs[subdir]: continue + if self._info['version'] == '37' and subdir == 'include': + subdir = 'include/%s' % self._info['modname'] + try: + cmake = CMakeFileEditor(os.path.join(subdir, 'CMakeLists.txt')) + except IOError: + continue + print "Traversing %s..." % subdir + filenames = cmake.find_filenames_match(self._info['pattern']) + yes = self._info['yes'] + for fname in filenames: + file_disabled = False + if not yes: + ans = raw_input("Really disable %s? [Y/n/a/q]: " % fname).lower().strip() + if ans == 'a': + yes = True + if ans == 'q': + sys.exit(0) + if ans == 'n': + continue + for special_treatment in special_treatments: + if special_treatment[0] == subdir and re.match(special_treatment[1], fname): + file_disabled = special_treatment[2](cmake, fname) + if not file_disabled: + cmake.disable_file(fname) + cmake.write() + print "Careful: 'gr_modtool disable' does not resolve dependencies." + diff --git a/gr-utils/src/python/modtool/modtool_help.py b/gr-utils/src/python/modtool/modtool_help.py new file mode 100644 index 000000000..a1dd3c466 --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_help.py @@ -0,0 +1,66 @@ +""" The help module """ + +from modtool_base import ModTool +from modtool_info import ModToolInfo +from modtool_add import ModToolAdd +from modtool_rm import ModToolRemove +from modtool_newmod import ModToolNewModule +from modtool_disable import ModToolDisable +from modtool_makexml import ModToolMakeXML +from util_functions import get_command_from_argv +from templates import Templates + +def get_class_dict(): + " Return a dictionary of the available commands in the form command->class " + classdict = {} + for g in globals().values(): + try: + if issubclass(g, ModTool): + classdict[g.name] = g + for a in g.aliases: + classdict[a] = g + except (TypeError, AttributeError): + pass + return classdict + + +### Help module ############################################################## +def print_class_descriptions(): + ''' Go through all ModTool* classes and print their name, + alias and description. ''' + desclist = [] + for gvar in globals().values(): + try: + if issubclass(gvar, ModTool) and not issubclass(gvar, ModToolHelp): + desclist.append((gvar.name, ','.join(gvar.aliases), gvar.__doc__)) + except (TypeError, AttributeError): + pass + print 'Name Aliases Description' + print '=====================================================================' + for description in desclist: + print '%-8s %-12s %s' % description + +class ModToolHelp(ModTool): + ''' Show some help. ''' + name = 'help' + aliases = ('h', '?') + def __init__(self): + ModTool.__init__(self) + + def setup(self): + pass + + def run(self): + cmd_dict = get_class_dict() + cmds = cmd_dict.keys() + cmds.remove(self.name) + for a in self.aliases: + cmds.remove(a) + help_requested_for = get_command_from_argv(cmds) + if help_requested_for is None: + print 'Usage:' + Templates['usage'] + print '\nList of possible commands:\n' + print_class_descriptions() + return + cmd_dict[help_requested_for]().setup_parser().print_help() + diff --git a/gr-utils/src/python/modtool/modtool_info.py b/gr-utils/src/python/modtool/modtool_info.py new file mode 100644 index 000000000..80fa27832 --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_info.py @@ -0,0 +1,137 @@ +""" Returns information about a module """ + +import os +from optparse import OptionGroup + +from modtool_base import ModTool +from util_functions import get_modname + +### Info module ############################################################## +class ModToolInfo(ModTool): + """ Return information about a given module """ + name = 'info' + aliases = ('getinfo', 'inf') + def __init__(self): + ModTool.__init__(self) + + def setup_parser(self): + " Initialise the option parser for 'gr_modtool.py info' " + parser = ModTool.setup_parser(self) + parser.usage = '%prog info [options]. \n Call %prog without any options to run it interactively.' + ogroup = OptionGroup(parser, "Info options") + ogroup.add_option("--python-readable", action="store_true", default=None, + help="Return the output in a format that's easier to read for Python scripts.") + ogroup.add_option("--suggested-dirs", default=None, type="string", + help="Suggest typical include dirs if nothing better can be detected.") + parser.add_option_group(ogroup) + return parser + + def setup(self): + # Won't call parent's setup(), because that's too chatty + (self.options, self.args) = self.parser.parse_args() + + def run(self): + """ Go, go, go! """ + mod_info = {} + mod_info['base_dir'] = self._get_base_dir(self.options.directory) + if mod_info['base_dir'] is None: + if self.options.python_readable: + print '{}' + else: + print "No module found." + exit(1) + os.chdir(mod_info['base_dir']) + mod_info['modname'] = get_modname() + if mod_info['modname'] is None: + if self.options.python_readable: + print '{}' + else: + print "No module found." + exit(1) + if self._info['version'] == '36' and os.path.isdir(os.path.join('include', mod_info['modname'])): + self._info['version'] = '37' + mod_info['version'] = self._info['version'] + if 'is_component' in self._info.keys(): + mod_info['is_component'] = True + mod_info['incdirs'] = [] + mod_incl_dir = os.path.join(mod_info['base_dir'], 'include') + if os.path.isdir(os.path.join(mod_incl_dir, mod_info['modname'])): + mod_info['incdirs'].append(os.path.join(mod_incl_dir, mod_info['modname'])) + else: + mod_info['incdirs'].append(mod_incl_dir) + build_dir = self._get_build_dir(mod_info) + if build_dir is not None: + mod_info['build_dir'] = build_dir + mod_info['incdirs'] += self._get_include_dirs(mod_info) + if self.options.python_readable: + print str(mod_info) + else: + self._pretty_print(mod_info) + + def _get_base_dir(self, start_dir): + """ Figure out the base dir (where the top-level cmake file is) """ + base_dir = os.path.abspath(start_dir) + if self._check_directory(base_dir): + return base_dir + else: + (up_dir, this_dir) = os.path.split(base_dir) + if os.path.split(up_dir)[1] == 'include': + up_dir = os.path.split(up_dir)[0] + if self._check_directory(up_dir): + return up_dir + return None + + def _get_build_dir(self, mod_info): + """ Figure out the build dir (i.e. where you run 'cmake'). This checks + for a file called CMakeCache.txt, which is created when running cmake. + If that hasn't happened, the build dir cannot be detected, unless it's + called 'build', which is then assumed to be the build dir. """ + base_build_dir = mod_info['base_dir'] + if 'is_component' in mod_info.keys(): + (base_build_dir, rest_dir) = os.path.split(base_build_dir) + has_build_dir = os.path.isdir(os.path.join(base_build_dir , 'build')) + if (has_build_dir and os.path.isfile(os.path.join(base_build_dir, 'CMakeCache.txt'))): + return os.path.join(base_build_dir, 'build') + else: + for (dirpath, dirnames, filenames) in os.walk(base_build_dir): + if 'CMakeCache.txt' in filenames: + return dirpath + if has_build_dir: + return os.path.join(base_build_dir, 'build') + return None + + def _get_include_dirs(self, mod_info): + """ Figure out include dirs for the make process. """ + inc_dirs = [] + path_or_internal = {True: 'INTERNAL', + False: 'PATH'}['is_component' in mod_info.keys()] + try: + cmakecache_fid = open(os.path.join(mod_info['build_dir'], 'CMakeCache.txt')) + for line in cmakecache_fid: + if line.find('GNURADIO_CORE_INCLUDE_DIRS:%s' % path_or_internal) != -1: + inc_dirs += line.replace('GNURADIO_CORE_INCLUDE_DIRS:%s=' % path_or_internal, '').strip().split(';') + if line.find('GRUEL_INCLUDE_DIRS:%s' % path_or_internal) != -1: + inc_dirs += line.replace('GRUEL_INCLUDE_DIRS:%s=' % path_or_internal, '').strip().split(';') + except IOError: + pass + if len(inc_dirs) == 0 and self.options.suggested_dirs is not None: + inc_dirs = [os.path.normpath(path) for path in self.options.suggested_dirs.split(':') if os.path.isdir(path)] + return inc_dirs + + def _pretty_print(self, mod_info): + """ Output the module info in human-readable format """ + index_names = {'base_dir': 'Base directory', + 'modname': 'Module name', + 'is_component': 'Is GR component', + 'build_dir': 'Build directory', + 'incdirs': 'Include directories'} + for key in mod_info.keys(): + if key == 'version': + print " API version: %s" % { + '36': 'pre-3.7', + '37': 'post-3.7', + 'autofoo': 'Autotools (pre-3.5)' + }[mod_info['version']] + else: + print '%19s: %s' % (index_names[key], mod_info[key]) + diff --git a/gr-utils/src/python/modtool/modtool_makexml.py b/gr-utils/src/python/modtool/modtool_makexml.py new file mode 100644 index 000000000..5a1a24f1b --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_makexml.py @@ -0,0 +1,151 @@ +""" Automatically create XML bindings for GRC from block code """ + +import sys +import os +import re +import glob +from optparse import OptionGroup + +from modtool_base import ModTool +from parser_cc_block import ParserCCBlock +from grc_xml_generator import GRCXMLGenerator +from cmakefile_editor import CMakeFileEditor + +### Remove module ########################################################### +class ModToolMakeXML(ModTool): + """ Make XML file for GRC block bindings """ + name = 'makexml' + aliases = ('mx',) + def __init__(self): + ModTool.__init__(self) + + def setup_parser(self): + " Initialise the option parser for 'gr_modtool.py makexml' " + parser = ModTool.setup_parser(self) + parser.usage = '%prog makexml [options]. \n Call %prog without any options to run it interactively.' + ogroup = OptionGroup(parser, "Make XML module options") + ogroup.add_option("-p", "--pattern", type="string", default=None, + help="Filter possible choices for blocks to be parsed.") + ogroup.add_option("-y", "--yes", action="store_true", default=False, + help="Answer all questions with 'yes'. This can overwrite existing files!") + parser.add_option_group(ogroup) + return parser + + def setup(self): + ModTool.setup(self) + options = self.options + if options.pattern is not None: + self._info['pattern'] = options.pattern + elif options.block_name is not None: + self._info['pattern'] = options.block_name + elif len(self.args) >= 2: + self._info['pattern'] = self.args[1] + else: + self._info['pattern'] = raw_input('Which blocks do you want to parse? (Regex): ') + if len(self._info['pattern']) == 0: + self._info['pattern'] = '.' + self._info['yes'] = options.yes + + def run(self): + """ Go, go, go! """ + print "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'] == '37': + 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_xml_from_block_data(params, iosig, blockname) + # 2) Go through python/ + + + def _search_files(self, path, path_glob): + """ Search for files matching pattern in the given path. """ + files = glob.glob("%s/%s"% (path, path_glob)) + files_filt = [] + print "Searching for matching files in %s/:" % 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: + print "None found." + return files_filt + + def _make_grc_xml_from_block_data(self, params, iosig, blockname): + """ Take the return values from the parser and call the XML + generator. Also, check the makefile if the .xml file is in there. + If necessary, add. """ + fname_xml = '%s_%s.xml' % (self._info['modname'], blockname) + # Some adaptions for the GRC + for inout in ('in', 'out'): + if iosig[inout]['max_ports'] == '-1': + iosig[inout]['max_ports'] = '$num_%sputs' % inout + params.append({'key': 'num_%sputs' % inout, + 'type': 'int', + 'name': 'Num %sputs' % inout, + 'default': '2', + 'in_constructor': False}) + if os.path.isfile(os.path.join('grc', fname_xml)): + # TODO add an option to keep + print "Warning: Overwriting existing GRC file." + grc_generator = GRCXMLGenerator( + modname=self._info['modname'], + blockname=blockname, + params=params, + iosig=iosig + ) + grc_generator.save(os.path.join('grc', fname_xml)) + if not self._skip_subdirs['grc']: + ed = CMakeFileEditor(self._file['cmgrc']) + if re.search(fname_xml, ed.cfile) is None and not ed.check_for_glob('*.xml'): + print "Adding GRC bindings to grc/CMakeLists.txt..." + ed.append_value('install', fname_xml, 'DESTINATION[^()]+') + ed.write() + + 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[: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.', '.') + blockname = blockname.replace(self._info['modname']+'_', '', 1) + return (blockname, fname_h) + # Go, go, go + print "Making GRC bindings for %s..." % fname_cc + (blockname, fname_h) = _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: + print "Can't open some of the files necessary to parse %s." % fname_cc + sys.exit(1) + return (parser.read_params(), parser.read_io_signature(), blockname) + + diff --git a/gr-utils/src/python/modtool/modtool_newmod.py b/gr-utils/src/python/modtool/modtool_newmod.py new file mode 100644 index 000000000..730b72e39 --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_newmod.py @@ -0,0 +1,88 @@ +""" Create a whole new out-of-tree module """ + +import os +import re +import sys +import base64 +import tarfile +from optparse import OptionGroup + +from modtool_base import ModTool +from newmod_tarfile import NEWMOD_TARFILE + +### New out-of-tree-mod module ############################################### +class ModToolNewModule(ModTool): + """ Create a new out-of-tree module """ + name = 'newmod' + aliases = ('nm', 'create') + def __init__(self): + ModTool.__init__(self) + + def setup_parser(self): + " Initialise the option parser for 'gr_modtool.py newmod' " + parser = ModTool.setup_parser(self) + parser.usage = '%prog rm [options]. \n Call %prog without any options to run it interactively.' + ogroup = OptionGroup(parser, "New out-of-tree module options") + parser.add_option_group(ogroup) + return parser + + def setup(self): + (options, self.args) = self.parser.parse_args() + self._info['modname'] = options.module_name + if self._info['modname'] is None: + if len(self.args) >= 2: + self._info['modname'] = self.args[1] + else: + self._info['modname'] = raw_input('Name of the new module: ') + if not re.match('[a-zA-Z0-9_]+', self._info['modname']): + print 'Invalid module name.' + sys.exit(2) + self._dir = options.directory + if self._dir == '.': + self._dir = './gr-%s' % self._info['modname'] + print 'Module directory is "%s".' % self._dir + try: + os.stat(self._dir) + except OSError: + pass # This is what should happen + else: + print 'The given directory exists.' + sys.exit(2) + + def run(self): + """ + * Unpack the tar.bz2 to the new locations + * Remove the bz2 + * Open all files, rename howto and HOWTO to the module name + * Rename files and directories that contain the word howto + """ + print "Creating directory..." + try: + os.mkdir(self._dir) + os.chdir(self._dir) + except OSError: + print 'Could not create directory %s. Quitting.' % self._dir + sys.exit(2) + print "Copying howto example..." + open('tmp.tar.bz2', 'wb').write(base64.b64decode(NEWMOD_TARFILE)) + print "Unpacking..." + tar = tarfile.open('tmp.tar.bz2', mode='r:bz2') + tar.extractall() + tar.close() + os.unlink('tmp.tar.bz2') + print "Replacing occurences of 'howto' to '%s'..." % self._info['modname'], + for root, dirs, files in os.walk('.'): + for filename in files: + f = os.path.join(root, filename) + s = open(f, 'r').read() + s = s.replace('howto', self._info['modname']) + s = s.replace('HOWTO', self._info['modname'].upper()) + open(f, 'w').write(s) + if filename.find('howto') != -1: + os.rename(f, os.path.join(root, filename.replace('howto', self._info['modname']))) + if os.path.basename(root) == 'howto': + os.rename(root, os.path.join(os.path.dirname(root), self._info['modname'])) + print "Done." + print "Use 'gr_modtool add' to add a new block to this currently empty module." + + diff --git a/gr-utils/src/python/modtool/modtool_rm.py b/gr-utils/src/python/modtool/modtool_rm.py new file mode 100644 index 000000000..16bfeb34c --- /dev/null +++ b/gr-utils/src/python/modtool/modtool_rm.py @@ -0,0 +1,155 @@ +""" Remove blocks module """ + +import os +import re +import sys +import glob +from optparse import OptionGroup + +from util_functions import remove_pattern_from_file +from modtool_base import ModTool +from cmakefile_editor import CMakeFileEditor + +### Remove module ########################################################### +class ModToolRemove(ModTool): + """ Remove block (delete files and remove Makefile entries) """ + name = 'remove' + aliases = ('rm', 'del') + def __init__(self): + ModTool.__init__(self) + + def setup_parser(self): + " Initialise the option parser for 'gr_modtool.py rm' " + parser = ModTool.setup_parser(self) + parser.usage = '%prog rm [options]. \n Call %prog without any options to run it interactively.' + ogroup = OptionGroup(parser, "Remove module options") + ogroup.add_option("-p", "--pattern", type="string", default=None, + help="Filter possible choices for blocks to be deleted.") + ogroup.add_option("-y", "--yes", action="store_true", default=False, + help="Answer all questions with 'yes'.") + parser.add_option_group(ogroup) + return parser + + def setup(self): + ModTool.setup(self) + options = self.options + if options.pattern is not None: + self._info['pattern'] = options.pattern + elif options.block_name is not None: + self._info['pattern'] = options.block_name + elif len(self.args) >= 2: + self._info['pattern'] = self.args[1] + else: + self._info['pattern'] = raw_input('Which blocks do you want to delete? (Regex): ') + if len(self._info['pattern']) == 0: + self._info['pattern'] = '.' + self._info['yes'] = options.yes + + def run(self): + """ Go, go, go! """ + def _remove_cc_test_case(filename=None, ed=None): + """ Special function that removes the occurrences of a qa*.cc file + from the CMakeLists.txt. """ + if filename[:2] != 'qa': + return + if self._info['version'] == '37': + (base, ext) = os.path.splitext(filename) + if ext == '.h': + remove_pattern_from_file(self._file['qalib'], + '^#include "%s"\s*$' % filename) + remove_pattern_from_file(self._file['qalib'], + '^\s*s->addTest\(gr::%s::%s::suite\(\)\);\s*$' % ( + self._info['modname'], base) + ) + elif ext == '.cc': + ed.remove_value('list', + '\$\{CMAKE_CURRENT_SOURCE_DIR\}/%s' % filename, + 'APPEND test_%s_sources' % self._info['modname']) + else: + filebase = os.path.splitext(filename)[0] + ed.delete_entry('add_executable', filebase) + ed.delete_entry('target_link_libraries', filebase) + ed.delete_entry('GR_ADD_TEST', filebase) + ed.remove_double_newlines() + + def _remove_py_test_case(filename=None, ed=None): + """ Special function that removes the occurrences of a qa*.{cc,h} file + from the CMakeLists.txt and the qa_$modname.cc. """ + if filename[:2] != 'qa': + return + filebase = os.path.splitext(filename)[0] + ed.delete_entry('GR_ADD_TEST', filebase) + ed.remove_double_newlines() + + def _make_swig_regex(filename): + filebase = os.path.splitext(filename)[0] + pyblockname = filebase.replace(self._info['modname'] + '_', '') + regexp = r'(^\s*GR_SWIG_BLOCK_MAGIC2?\(%s,\s*%s\);|^\s*.include\s*"(%s/)?%s"\s*)' % \ + (self._info['modname'], pyblockname, self._info['modname'], filename) + return regexp + # Go, go, go! + if not self._skip_subdirs['lib']: + self._run_subdir('lib', ('*.cc', '*.h'), ('add_library',), + cmakeedit_func=_remove_cc_test_case) + if not self._skip_subdirs['include']: + incl_files_deleted = self._run_subdir(self._info['includedir'], ('*.h',), ('install',)) + if not self._skip_subdirs['swig']: + swig_files_deleted = self._run_subdir('swig', ('*.i',), ('install',)) + for f in incl_files_deleted + swig_files_deleted: + # TODO do this on all *.i files + remove_pattern_from_file(self._file['swig'], _make_swig_regex(f)) + if not self._skip_subdirs['python']: + py_files_deleted = self._run_subdir('python', ('*.py',), ('GR_PYTHON_INSTALL',), + cmakeedit_func=_remove_py_test_case) + for f in py_files_deleted: + remove_pattern_from_file(self._file['pyinit'], '.*import\s+%s.*' % f[:-3]) + remove_pattern_from_file(self._file['pyinit'], '.*from\s+%s\s+import.*\n' % f[:-3]) + if not self._skip_subdirs['grc']: + self._run_subdir('grc', ('*.xml',), ('install',)) + + + def _run_subdir(self, path, globs, makefile_vars, cmakeedit_func=None): + """ Delete all files that match a certain pattern in path. + path - The directory in which this will take place + globs - A tuple of standard UNIX globs of files to delete (e.g. *.xml) + makefile_vars - A tuple with a list of CMakeLists.txt-variables which + may contain references to the globbed files + cmakeedit_func - If the CMakeLists.txt needs special editing, use this + """ + # 1. Create a filtered list + files = [] + for g in globs: + files = files + glob.glob("%s/%s"% (path, g)) + files_filt = [] + print "Searching for matching files in %s/:" % 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: + print "None found." + return [] + # 2. Delete files, Makefile entries and other occurences + files_deleted = [] + ed = CMakeFileEditor('%s/CMakeLists.txt' % path) + yes = self._info['yes'] + for f in files_filt: + b = os.path.basename(f) + if not yes: + ans = raw_input("Really delete %s? [Y/n/a/q]: " % f).lower().strip() + if ans == 'a': + yes = True + if ans == 'q': + sys.exit(0) + if ans == 'n': + continue + files_deleted.append(b) + print "Deleting %s." % f + os.unlink(f) + print "Deleting occurrences of %s from %s/CMakeLists.txt..." % (b, path) + for var in makefile_vars: + ed.remove_value(var, b) + if cmakeedit_func is not None: + cmakeedit_func(b, ed) + ed.write() + return files_deleted + diff --git a/gr-utils/src/python/modtool/newmod_tarfile.py b/gr-utils/src/python/modtool/newmod_tarfile.py new file mode 100644 index 000000000..06d7e8f90 --- /dev/null +++ b/gr-utils/src/python/modtool/newmod_tarfile.py @@ -0,0 +1,1034 @@ +### The entire new module zipfile as base64 encoded tar.bz2 ### +NEWMOD_TARFILE = """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""" diff --git a/gr-utils/src/python/modtool/parser_cc_block.py b/gr-utils/src/python/modtool/parser_cc_block.py new file mode 100644 index 000000000..447fe113d --- /dev/null +++ b/gr-utils/src/python/modtool/parser_cc_block.py @@ -0,0 +1,205 @@ +''' A parser for blocks written in C++ ''' +import re +import sys + +### Parser for CC blocks #################################################### +def dummy_translator(the_type, default_v=None): + """ Doesn't really translate. """ + return the_type + +class ParserCCBlock(object): + """ Class to read blocks written in C++ """ + def __init__(self, filename_cc, filename_h, blockname, version, type_trans=dummy_translator): + self.code_cc = open(filename_cc).read() + self.code_h = open(filename_h).read() + self.blockname = blockname + self.type_trans = type_trans + self.version = version + + def read_io_signature(self): + """ Scans a .cc file for an IO signature. """ + def _figure_out_iotype_and_vlen(iosigcall, typestr): + """ From a type identifier, returns the data type. + E.g., for sizeof(int), it will return 'int'. + Returns a list! """ + if 'gr_make_iosignaturev' in iosigcall: + print 'tbi' + raise ValueError + return {'type': [_typestr_to_iotype(x) for x in typestr.split(',')], + 'vlen': [_typestr_to_vlen(x) for x in typestr.split(',')] + } + def _typestr_to_iotype(typestr): + """ Convert a type string (e.g. sizeof(int) * vlen) to the type (e.g. 'int'). """ + type_match = re.search('sizeof\s*\(([^)]*)\)', typestr) + if type_match is None: + return self.type_trans('char') + return self.type_trans(type_match.group(1)) + def _typestr_to_vlen(typestr): + """ From a type identifier, returns the vector length of the block's + input/out. E.g., for 'sizeof(int) * 10', it returns 10. For + 'sizeof(int)', it returns '1'. For 'sizeof(int) * vlen', it returns + the string vlen. """ + # Catch fringe case where no sizeof() is given + if typestr.find('sizeof') == -1: + return typestr + if typestr.find('*') == -1: + return '1' + vlen_parts = typestr.split('*') + for fac in vlen_parts: + if fac.find('sizeof') != -1: + vlen_parts.remove(fac) + if len(vlen_parts) == 1: + return vlen_parts[0].strip() + elif len(vlen_parts) > 1: + return '*'.join(vlen_parts).strip() + iosig = {} + iosig_regex = '(?P<incall>gr_make_io_signature[23v]?)\s*\(\s*(?P<inmin>[^,]+),\s*(?P<inmax>[^,]+),' + \ + '\s*(?P<intype>(\([^\)]*\)|[^)])+)\),\s*' + \ + '(?P<outcall>gr_make_io_signature[23v]?)\s*\(\s*(?P<outmin>[^,]+),\s*(?P<outmax>[^,]+),' + \ + '\s*(?P<outtype>(\([^\)]*\)|[^)])+)\)' + iosig_match = re.compile(iosig_regex, re.MULTILINE).search(self.code_cc) + try: + iosig['in'] = _figure_out_iotype_and_vlen(iosig_match.group('incall'), + iosig_match.group('intype')) + iosig['in']['min_ports'] = iosig_match.group('inmin') + iosig['in']['max_ports'] = iosig_match.group('inmax') + except ValueError, Exception: + print "Error: Can't parse input signature." + try: + iosig['out'] = _figure_out_iotype_and_vlen(iosig_match.group('outcall'), + iosig_match.group('outtype')) + iosig['out']['min_ports'] = iosig_match.group('outmin') + iosig['out']['max_ports'] = iosig_match.group('outmax') + except ValueError, Exception: + print "Error: Can't parse output signature." + return iosig + + + def read_params(self): + """ Read the parameters required to initialize the block """ + def _scan_param_list(start_idx): + """ Go through a parameter list and return a tuple each: + (type, name, default_value). Python's re just doesn't cut + it for C++ code :( """ + i = start_idx + c = self.code_h + if c[i] != '(': + raise ValueError + i += 1 + + param_list = [] + read_state = 'type' + in_string = False + parens_count = 0 # Counts () + brackets_count = 0 # Counts <> + end_of_list = False + this_type = '' + this_name = '' + this_defv = '' + WHITESPACE = ' \t\n\r\f\v' + while not end_of_list: + # Keep track of (), stop when reaching final closing parens + if not in_string: + if c[i] == ')': + if parens_count == 0: + if read_state == 'type': + raise ValueError( + 'Found closing parentheses before finishing last argument (this is how far I got: %s)' + % str(param_list) + ) + param_list.append((this_type, this_name, this_defv)) + end_of_list = True + break + else: + parens_count -= 1 + elif c[i] == '(': + parens_count += 1 + # Parameter type (int, const std::string, std::vector<gr_complex>, unsigned long ...) + if read_state == 'type': + if c[i] == '<': + brackets_count += 1 + if c[i] == '>': + brackets_count -= 1 + if c[i] == '&': + i += 1 + continue + if c[i] in WHITESPACE and brackets_count == 0: + while c[i] in WHITESPACE: + i += 1 + continue + if this_type == 'const' or this_type == '': # Ignore this + this_type = '' + elif this_type == 'unsigned': # Continue + this_type += ' ' + continue + else: + read_state = 'name' + continue + this_type += c[i] + i += 1 + continue + # Parameter name + if read_state == 'name': + if c[i] == '&' or c[i] in WHITESPACE: + i += 1 + elif c[i] == '=': + if parens_count != 0: + raise ValueError( + 'While parsing argument %d (%s): name finished but no closing parentheses.' + % (len(param_list)+1, this_type + ' ' + this_name) + ) + read_state = 'defv' + i += 1 + elif c[i] == ',': + if parens_count: + raise ValueError( + 'While parsing argument %d (%s): name finished but no closing parentheses.' + % (len(param_list)+1, this_type + ' ' + this_name) + ) + read_state = 'defv' + else: + this_name += c[i] + i += 1 + continue + # Default value + if read_state == 'defv': + if in_string: + if c[i] == '"' and c[i-1] != '\\': + in_string = False + else: + this_defv += c[i] + elif c[i] == ',': + if parens_count: + raise ValueError( + 'While parsing argument %d (%s): default value finished but no closing parentheses.' + % (len(param_list)+1, this_type + ' ' + this_name) + ) + read_state = 'type' + param_list.append((this_type, this_name, this_defv)) + this_type = '' + this_name = '' + this_defv = '' + else: + this_defv += c[i] + i += 1 + continue + return param_list + # Go, go, go! + if self.version == '37': + make_regex = 'static\s+sptr\s+make\s*' + else: + make_regex = '(?<=_API)\s+\w+_sptr\s+\w+_make_\w+\s*' + make_match = re.compile(make_regex, re.MULTILINE).search(self.code_h) + try: + params_list = _scan_param_list(make_match.end(0)) + except ValueError as ve: + print "Can't parse the argument list: ", ve.args[0] + sys.exit(0) + params = [] + for plist in params_list: + params.append({'type': self.type_trans(plist[0], plist[2]), + 'key': plist[1], + 'default': plist[2], + 'in_constructor': True}) + return params + diff --git a/gr-utils/src/python/modtool/templates.py b/gr-utils/src/python/modtool/templates.py new file mode 100644 index 000000000..e3019bb70 --- /dev/null +++ b/gr-utils/src/python/modtool/templates.py @@ -0,0 +1,698 @@ +''' All the templates for skeleton files (needed by ModToolAdd) ''' + +from datetime import datetime + +### Templates ################################################################ +Templates = {} +Templates36 = {} + +# Default licence +Templates['defaultlicense'] = ''' +Copyright %d <+YOU OR YOUR COMPANY+>. + +This 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. + +This software 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 software; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, +Boston, MA 02110-1301, USA. +''' % datetime.now().year + +# Header file of a sync/decimator/interpolator block +Templates['block_impl_h'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} +\#ifndef INCLUDED_${modname.upper()}_${blockname.upper()}_IMPL_H +\#define INCLUDED_${modname.upper()}_${blockname.upper()}_IMPL_H + +\#include <${modname}/${blockname}.h> + +namespace gr { + namespace ${modname} { + + class ${blockname}_impl : public ${blockname} + { + private: + // Nothing to declare in this block. + + public: + ${blockname}_impl(${strip_default_values($arglist)}); + ~${blockname}_impl(); + +#if $blocktype == 'general' + void forecast (int noutput_items, gr_vector_int &ninput_items_required); + + // Where all the action really happens + int general_work(int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +#else if $blocktype == 'hier' +#silent pass +#else + // Where all the action really happens + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +#end if + }; + + } // namespace ${modname} +} // namespace gr + +\#endif /* INCLUDED_${modname.upper()}_${blockname.upper()}_IMPL_H */ + +''' + +# C++ file of a GR block +Templates['block_impl_cpp'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} +\#ifdef HAVE_CONFIG_H +\#include "config.h" +\#endif + +\#include <gr_io_signature.h> +#if $blocktype == 'noblock' +\#include <${modname}/${blockname}.h> +#else +\#include "${blockname}_impl.h" +#end if + +namespace gr { + namespace ${modname} { + +#if $blocktype == 'noblock' + $blockname::${blockname}(${strip_default_values($arglist)}) + { + } + + $blockname::~${blockname}() + { + } +#else + ${blockname}::sptr + ${blockname}::make(${strip_default_values($arglist)}) + { + return gnuradio::get_initial_sptr (new ${blockname}_impl(${strip_arg_types($arglist)})); + } + +#if $blocktype == 'decimator' +#set $decimation = ', <+decimation+>' +#else if $blocktype == 'interpolator' +#set $decimation = ', <+interpolation+>' +#else +#set $decimation = '' +#end if +#if $blocktype == 'source' +#set $inputsig = '0, 0, 0' +#else +#set $inputsig = '<+MIN_IN+>, <+MAX_IN+>, sizeof (<+float+>)' +#end if +#if $blocktype == 'sink' +#set $outputsig = '0, 0, 0' +#else +#set $outputsig = '<+MIN_IN+>, <+MAX_IN+>, sizeof (<+float+>)' +#end if + /* + * The private constructor + */ + ${blockname}_impl::${blockname}_impl(${strip_default_values($arglist)}) + : ${grblocktype}("${blockname}", + gr_make_io_signature($inputsig), + gr_make_io_signature($outputsig)$decimation) +#if $blocktype == 'hier' + { + connect(self(), 0, d_firstblock, 0); + // connect other blocks + connect(d_lastblock, 0, self(), 0); + } +#else + {} +#end if + + /* + * Our virtual destructor. + */ + ${blockname}_impl::~${blockname}_impl() + { + } + +#if $blocktype == 'general' + void + ${blockname}_impl::forecast (int noutput_items, gr_vector_int &ninput_items_required) + { + /* <+forecast+> e.g. ninput_items_required[0] = noutput_items */ + } + + int + ${blockname}_impl::general_work (int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + const float *in = (const float *) input_items[0]; + float *out = (float *) output_items[0]; + + // Do <+signal processing+> + // Tell runtime system how many input items we consumed on + // each input stream. + consume_each (noutput_items); + + // Tell runtime system how many output items we produced. + return noutput_items; + } +#else if $blocktype == 'hier' +#silent pass +#else + int + ${blockname}_impl::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + const float *in = (const float *) input_items[0]; + float *out = (float *) output_items[0]; + + // Do <+signal processing+> + + // Tell runtime system how many output items we produced. + return noutput_items; + } +#end if +#end if + + } /* namespace ${modname} */ +} /* namespace gr */ + +''' + +# Block definition header file (for include/) +Templates['block_def_h'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} + +\#ifndef INCLUDED_${modname.upper()}_${blockname.upper()}_H +\#define INCLUDED_${modname.upper()}_${blockname.upper()}_H + +\#include <${modname}/api.h> +\#include <${grblocktype}.h> + +namespace gr { + namespace ${modname} { + +#if $blocktype == 'noblock' + /*! + * \\brief <+description+> + * + */ + class ${modname.upper()}_API $blockname + { + ${blockname}(${arglist}); + ~${blockname}(); + private: + }; +#else + /*! + * \\brief <+description of block+> + * \ingroup ${modname} + * + */ + class ${modname.upper()}_API ${blockname} : virtual public $grblocktype + { + public: + typedef boost::shared_ptr<${blockname}> sptr; + + /*! + * \\brief Return a shared_ptr to a new instance of ${modname}::${blockname}. + * + * To avoid accidental use of raw pointers, ${modname}::${blockname}'s + * constructor is in a private implementation + * class. ${modname}::${blockname}::make is the public interface for + * creating new instances. + */ + static sptr make($arglist); + }; +#end if + + } // namespace ${modname} +} // namespace gr + +\#endif /* INCLUDED_${modname.upper()}_${blockname.upper()}_H */ + +''' + +# Python block (from grextras!) +Templates['block_python'] = '''\#!/usr/bin/env python +${str_to_python_comment($license)} +# +#if $blocktype == 'noblock' +#stop +#end if + +#if $blocktype in ('sync', 'sink', 'source') +#set $parenttype = 'gr.sync_block' +#else +#set $parenttype = {'hier': 'gr.hier_block2', 'interpolator': 'gr.interp_block', 'decimator': 'gr.decim_block', 'general': 'gr.block'}[$blocktype] +#end if +#if $blocktype != 'hier' +import numpy +#if $blocktype == 'source' +#set $inputsig = 'None' +#else +#set $inputsig = '[<+numpy.float+>]' +#end if +#if $blocktype == 'sink' +#set $outputsig = 'None' +#else +#set $outputsig = '[<+numpy.float+>]' +#end if +#else +#if $blocktype == 'source' +#set $inputsig = '0, 0, 0' +#else +#set $inputsig = '<+MIN_IN+>, <+MAX_IN+>, gr.sizeof_<+float+>' +#end if +#if $blocktype == 'sink' +#set $outputsig = '0, 0, 0' +#else +#set $outputsig = '<+MIN_OUT+>, <+MAX_OUT+>, gr.sizeof_<+float+>' +#end if +#end if +#if $blocktype == 'interpolator' +#set $deciminterp = ', <+interpolation+>' +#else if $blocktype == 'decimator' +#set $deciminterp = ', <+decimation+>' +#set $deciminterp = '' +#else +#end if +from gnuradio import gr + +class ${blockname}(${parenttype}): + """ + docstring for block ${blockname} + """ + def __init__(self#if $arglist == '' then '' else ', '#$arglist): + gr.${parenttype}.__init__(self, +#if $blocktype == 'hier' + "$blockname", + gr.io_signature(${inputsig}), # Input signature + gr.io_signature(${outputsig})) # Output signature + + # Define blocks and connect them + self.connect() +#stop +#else + name="${blockname}", + in_sig=${inputsig}, + out_sig=${outputsig}${deciminterp}) +#end if + +#if $blocktype == 'general' + def forecast(self, noutput_items, ninput_items_required): + #setup size of input_items[i] for work call + for i in range(len(ninput_items_required)): + ninput_items_required[i] = noutput_items + + def general_work(self, input_items, output_items): + output_items[0][:] = input_items[0] + consume(0, len(input_items[0]) + \#self.consume_each(len(input_items[0])) + return len(output_items[0]) +#stop +#else + def work(self, input_items, output_items): +#end if + + def work(self, input_items, output_items): +#if $blocktype != 'source' + in0 = input_items[0] +#end if +#if $blocktype != 'sink' + out = output_items[0] +#end if + # <+signal processing here+> +#if $blocktype in ('sync', 'decimator', 'interpolator') + out[:] = in0 + return len(output_items[0]) +#else if $blocktype == 'sink' + return len(input_items[0]) +#else if $blocktype == 'source' + out[:] = whatever + return len(output_items[0]) +#end if + +''' + +# C++ file for QA +Templates['qa_cpp'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} + +\#include "qa_${blockname}.h" +\#include <cppunit/TestAssert.h> + +\#include <$modname/${blockname}.h> + +namespace gr { + namespace ${modname} { + + void + qa_${blockname}::t1() + { + // Put test here + } + + } /* namespace ${modname} */ +} /* namespace gr */ + +''' + +# Header file for QA +Templates['qa_h'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} + +\#ifndef _QA_${blockname.upper()}_H_ +\#define _QA_${blockname.upper()}_H_ + +\#include <cppunit/extensions/HelperMacros.h> +\#include <cppunit/TestCase.h> + +namespace gr { + namespace ${modname} { + + class qa_${blockname} : public CppUnit::TestCase + { + public: + CPPUNIT_TEST_SUITE(qa_${blockname}); + CPPUNIT_TEST(t1); + CPPUNIT_TEST_SUITE_END(); + + private: + void t1(); + }; + + } /* namespace ${modname} */ +} /* namespace gr */ + +\#endif /* _QA_${blockname.upper()}_H_ */ + +''' + +# Python QA code +Templates['qa_python'] = '''\#!/usr/bin/env python +${str_to_python_comment($license)} +# + +from gnuradio import gr, gr_unittest +#if $lang == 'cpp' +import ${modname}_swig as ${modname} +#else +from ${blockname} import ${blockname} +#end if + +class qa_$blockname (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001_t (self): + # set up fg + self.tb.run () + # check data + + +if __name__ == '__main__': + gr_unittest.run(qa_${blockname}, "qa_${blockname}.xml") +''' + +Templates['grc_xml'] = '''<?xml version="1.0"?> +<block> + <name>$blockname</name> + <key>${modname}_$blockname</key> + <category>$modname</category> + <import>import $modname</import> + <make>${modname}.${blockname}(${strip_arg_types($arglist)})</make> + <!-- Make one 'param' node for every Parameter you want settable from the GUI. + Sub-nodes: + * name + * key (makes the value accessible as \$keyname, e.g. in the make node) + * type --> + <param> + <name>...</name> + <key>...</key> + <type>...</type> + </param> + + <!-- Make one 'sink' node per input. Sub-nodes: + * name (an identifier for the GUI) + * type + * vlen + * optional (set to 1 for optional inputs) --> + <sink> + <name>in</name> + <type><!-- e.g. int, real, complex, byte, short, xxx_vector, ...--></type> + </sink> + + <!-- Make one 'source' node per output. Sub-nodes: + * name (an identifier for the GUI) + * type + * vlen + * optional (set to 1 for optional inputs) --> + <source> + <name>out</name> + <type><!-- e.g. int, real, complex, byte, short, xxx_vector, ...--></type> + </source> +</block> +''' + +# Usage +Templates['usage'] = ''' +gr_modtool.py <command> [options] -- Run <command> with the given options. +gr_modtool.py help -- Show a list of commands. +gr_modtool.py help <command> -- Shows the help for a given command. ''' + +# SWIG string +Templates['swig_block_magic'] = """#if $version == '37' +#set $mod_block_sep = '/' +#set $block_magic_version = '2' +#else +#set $mod_block_sep = '_' +#set $block_magic_version = '' +#end if +%include "${modname}${mod_block_sep}${blockname}.h" +GR_SWIG_BLOCK_MAGIC${block_magic_version}($modname, $blockname); +""" + +## Old stuff +# C++ file of a GR block +Templates['block_cpp36'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} +\#ifdef HAVE_CONFIG_H +\#include "config.h" +\#endif + +#if $blocktype != 'noblock' +\#include <gr_io_signature.h> +#end if +\#include "${modname}_${blockname}.h" + +#if $blocktype == 'noblock' +${modname}_${blockname}::${modname}_${blockname}(${strip_default_values($arglist)}) +{ +} + +${modname}_${blockname}::~${modname}_${blockname}() +{ +} +#else +${modname}_${blockname}_sptr +${modname}_make_${blockname} (${strip_default_values($arglist)}) +{ + return gnuradio::get_initial_sptr (new ${modname}_${blockname}(${strip_arg_types($arglist)})); +} + +#if $blocktype == 'decimator' +#set $decimation = ', <+decimation+>' +#else if $blocktype == 'interpolator' +#set $decimation = ', <+interpolation+>' +#else +#set $decimation = '' +#end if +#if $blocktype == 'sink' +#set $inputsig = '0, 0, 0' +#else +#set $inputsig = '<+MIN_IN+>, <+MAX_IN+>, sizeof (<+float+>)' +#end if +#if $blocktype == 'source' +#set $outputsig = '0, 0, 0' +#else +#set $outputsig = '<+MIN_OUT+>, <+MAX_OUT+>, sizeof (<+float+>)' +#end if + +/* + * The private constructor + */ +${modname}_${blockname}::${modname}_${blockname} (${strip_default_values($arglist)}) + : ${grblocktype} ("${blockname}", + gr_make_io_signature($inputsig), + gr_make_io_signature($outputsig)$decimation) +{ +#if $blocktype == 'hier' + connect(self(), 0, d_firstblock, 0); + // <+connect other blocks+> + connect(d_lastblock, 0, self(), 0); +#else + // Put in <+constructor stuff+> here +#end if +} + + +/* + * Our virtual destructor. + */ +${modname}_${blockname}::~${modname}_${blockname}() +{ + // Put in <+destructor stuff+> here +} +#end if + + +#if $blocktype == 'general' +void +${modname}_${blockname}::forecast (int noutput_items, gr_vector_int &ninput_items_required) +{ + /* <+forecast+> e.g. ninput_items_required[0] = noutput_items */ +} + +int +${modname}_${blockname}::general_work (int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + const float *in = (const float *) input_items[0]; + float *out = (float *) output_items[0]; + + // Do <+signal processing+> + // Tell runtime system how many input items we consumed on + // each input stream. + consume_each (noutput_items); + + // Tell runtime system how many output items we produced. + return noutput_items; +} +#else if $blocktype == 'hier' or $blocktype == 'noblock' +#pass +#else +int +${modname}_${blockname}::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + const float *in = (const float *) input_items[0]; + float *out = (float *) output_items[0]; + + // Do <+signal processing+> + + // Tell runtime system how many output items we produced. + return noutput_items; +} +#end if + +''' + +# Block definition header file (for include/) +Templates['block_h36'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} + +\#ifndef INCLUDED_${modname.upper()}_${blockname.upper()}_H +\#define INCLUDED_${modname.upper()}_${blockname.upper()}_H + +\#include <${modname}_api.h> +#if $blocktype == 'noblock' +class ${modname.upper()}_API $blockname +{ + ${blockname}(${arglist}); + ~${blockname}(); + private: +}; + +#else +\#include <${grblocktype}.h> + +class ${modname}_${blockname}; + +typedef boost::shared_ptr<${modname}_${blockname}> ${modname}_${blockname}_sptr; + +${modname.upper()}_API ${modname}_${blockname}_sptr ${modname}_make_${blockname} ($arglist); + +/*! + * \\brief <+description+> + * \ingroup ${modname} + * + */ +class ${modname.upper()}_API ${modname}_${blockname} : public $grblocktype +{ + private: + friend ${modname.upper()}_API ${modname}_${blockname}_sptr ${modname}_make_${blockname} (${strip_default_values($arglist)}); + + ${modname}_${blockname}(${strip_default_values($arglist)}); + + public: + ~${modname}_${blockname}(); + +#if $blocktype == 'general' + void forecast (int noutput_items, gr_vector_int &ninput_items_required); + + // Where all the action really happens + int general_work (int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +#else if $blocktype == 'hier' +#pass +#else + // Where all the action really happens + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +#end if +}; +#end if + +\#endif /* INCLUDED_${modname.upper()}_${blockname.upper()}_H */ + +''' + +# C++ file for QA +Templates['qa_cpp36'] = '''/* -*- c++ -*- */ +${str_to_fancyc_comment($license)} + +\#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_CASE(qa_${modname}_${blockname}_t1){ + BOOST_CHECK_EQUAL(2 + 2, 4); + // TODO BOOST_* test macros here +} + +BOOST_AUTO_TEST_CASE(qa_${modname}_${blockname}_t2){ + BOOST_CHECK_EQUAL(2 + 2, 4); + // TODO BOOST_* test macros here +} + +''' + +# Header file for QA +Templates['qa_cmakeentry36'] = """ +add_executable($basename $filename) +target_link_libraries($basename gnuradio-$modname \${Boost_LIBRARIES}) +GR_ADD_TEST($basename $basename) +""" + diff --git a/gr-utils/src/python/modtool/util_functions.py b/gr-utils/src/python/modtool/util_functions.py new file mode 100644 index 000000000..029ae04bf --- /dev/null +++ b/gr-utils/src/python/modtool/util_functions.py @@ -0,0 +1,127 @@ +""" Utility functions for gr_modtool.py """ + +import re +import sys + +### Utility functions ######################################################## +def get_command_from_argv(possible_cmds): + """ Read the requested command from argv. This can't be done with optparse, + since the option parser isn't defined before the command is known, and + optparse throws an error.""" + command = None + for arg in sys.argv: + if arg[0] == "-": + continue + else: + command = arg + if command in possible_cmds: + return arg + return None + +def append_re_line_sequence(filename, linepattern, newline): + """Detects the re 'linepattern' in the file. After its last occurrence, + paste 'newline'. If the pattern does not exist, append the new line + to the file. Then, write. """ + oldfile = open(filename, 'r').read() + lines = re.findall(linepattern, oldfile, flags=re.MULTILINE) + if len(lines) == 0: + open(filename, 'a').write(newline) + return + last_line = lines[-1] + newfile = oldfile.replace(last_line, last_line + newline + '\n') + open(filename, 'w').write(newfile) + +def remove_pattern_from_file(filename, pattern): + """ Remove all occurrences of a given pattern from a file. """ + oldfile = open(filename, 'r').read() + pattern = re.compile(pattern, re.MULTILINE) + open(filename, 'w').write(pattern.sub('', oldfile)) + +def str_to_fancyc_comment(text): + """ Return a string as a C formatted comment. """ + l_lines = text.splitlines() + outstr = "/* " + l_lines[0] + "\n" + for line in l_lines[1:]: + outstr += " * " + line + "\n" + outstr += " */\n" + return outstr + +def str_to_python_comment(text): + """ Return a string as a Python formatted comment. """ + return re.compile('^', re.MULTILINE).sub('# ', text) + +def strip_default_values(string): + """ Strip default values from a C++ argument list. """ + return re.sub(' *=[^,)]*', '', string) + +def strip_arg_types(string): + """" Strip the argument types from a list of arguments + Example: "int arg1, double arg2" -> "arg1, arg2" """ + string = strip_default_values(string) + return ", ".join([part.strip().split(' ')[-1] for part in string.split(',')]) + +def get_modname(): + """ Grep the current module's name from gnuradio.project or CMakeLists.txt """ + modname_trans = {'howto-write-a-block': 'howto'} + try: + prfile = open('gnuradio.project', 'r').read() + regexp = r'projectname\s*=\s*([a-zA-Z0-9-_]+)$' + return re.search(regexp, prfile, flags=re.MULTILINE).group(1).strip() + except IOError: + pass + # OK, there's no gnuradio.project. So, we need to guess. + cmfile = open('CMakeLists.txt', 'r').read() + regexp = r'(project\s*\(\s*|GR_REGISTER_COMPONENT\(")gr-(?P<modname>[a-zA-Z1-9-_]+)(\s*(CXX)?|" ENABLE)' + try: + modname = re.search(regexp, cmfile, flags=re.MULTILINE).group('modname').strip() + if modname in modname_trans.keys(): + modname = modname_trans[modname] + return modname + except AttributeError: + return None + +def get_class_dict(): + " Return a dictionary of the available commands in the form command->class " + classdict = {} + for g in globals().values(): + try: + if issubclass(g, ModTool): + classdict[g.name] = g + for a in g.aliases: + classdict[a] = g + except (TypeError, AttributeError): + pass + return classdict + +def is_number(s): + " Return True if the string s contains a number. " + try: + float(s) + return True + except ValueError: + return False + +def xml_indent(elem, level=0): + """ Adds indents to XML for pretty printing """ + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + xml_indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +def ask_yes_no(question, default): + """ Asks a binary question. Returns True for yes, False for no. + default is given as a boolean. """ + question += {True: ' [Y/n] ', False: ' [y/N] '}[default] + if raw_input(question).lower() != {True: 'n', False: 'y'}[default]: + return default + else: + return not default |