diff options
88 files changed, 2724 insertions, 1276 deletions
diff --git a/gr-utils/CMakeLists.txt b/gr-utils/CMakeLists.txt index a8dfd91531..2578471b95 100644 --- a/gr-utils/CMakeLists.txt +++ b/gr-utils/CMakeLists.txt @@ -51,5 +51,4 @@ if(ENABLE_GR_UTILS) add_subdirectory(python/utils) add_subdirectory(python/modtool) - endif(ENABLE_GR_UTILS) diff --git a/gr-utils/python/modtool/CMakeLists.txt b/gr-utils/python/modtool/CMakeLists.txt index 5fc5b0287f..2cb7c21487 100644 --- a/gr-utils/python/modtool/CMakeLists.txt +++ b/gr-utils/python/modtool/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2011 Free Software Foundation, Inc. +# Copyright 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -21,28 +21,10 @@ include(GrPython) 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_info.py - modtool_makexml.py - modtool_newmod.py - modtool_rm.py - modtool_rename.py - parser_cc_block.py - scm.py - templates.py - util_functions.py DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool ) -set(GR_PKG_MODTOOL_DATA_DIR ${GR_PKG_DATA_DIR}/modtool) -install(DIRECTORY gr-newmod - DESTINATION ${GR_PKG_MODTOOL_DATA_DIR} -) +set(GR_PKG_MODTOOL_DATA_DIR ${GR_PKG_DATA_DIR}/modtool/templates) ######################################################################## @@ -60,3 +42,11 @@ install( DESTINATION ${GR_PREFSDIR} ) +######################################################################## +# Add subdirectories +######################################################################## +add_subdirectory(core) +add_subdirectory(cli) +add_subdirectory(tools) +add_subdirectory(templates) +add_subdirectory(tests) diff --git a/gr-utils/python/modtool/README.modtool b/gr-utils/python/modtool/README.modtool index 84c2e66c11..bffe49e9e2 100644 --- a/gr-utils/python/modtool/README.modtool +++ b/gr-utils/python/modtool/README.modtool @@ -1,14 +1,25 @@ gr_modtool: Swiss Army Knife for editing GNU Radio modules and -components. -Adding a new subcommand -======================= +Adding a new subcommand for Command Line Interface +================================================== -* Add a new file called modtool_SUBCOMMAND +* Add a new file called SUBCOMMAND in the cli directory +* Create a function cli with the decorator @click.command or @click.group +* Add the necessary options for the command or command group +* Add that file to __init__.py and CMakeLists.txt in the cli directory + + +Adding a new subcommand for Exposing as an API +============================================== + +* Add a new file called SUBCOMMAND in the core directory * Have a look at the other subcommands, it must inherit from ModTool -* Add that file to __init__.py and CMakeLists.txt +* Add that file to __init__.py and CMakeLists.txt in the core directory + + +The gr-newmod directory inside the templates directory +====================================================== -The gr-newmod directory -======================= This dir basically contains a copy of gr-howto-write-a-block from the gnuradio sources, with some differences: - All example blocks, apps, grc files (...) and references thereto in the @@ -16,4 +27,3 @@ sources, with some differences: - In the top-level CMake file, the project is called 'gr-howto'. - Any time anything relevant is changed in gr-howto-write-a-block, it should be changed here, too. - diff --git a/gr-utils/python/modtool/__init__.py b/gr-utils/python/modtool/__init__.py index b6d719ac1d..e69de29bb2 100644 --- a/gr-utils/python/modtool/__init__.py +++ b/gr-utils/python/modtool/__init__.py @@ -1,37 +0,0 @@ -from __future__ import absolute_import -from __future__ import unicode_literals -# -# Copyright 2013-2014 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 .grc_xml_generator import GRCXMLGenerator -from .modtool_base import ModTool, ModToolException, get_modtool_modules -from .modtool_add import ModToolAdd -from .modtool_disable import ModToolDisable -from .modtool_info import ModToolInfo -from .modtool_makexml import ModToolMakeXML -from .modtool_newmod import ModToolNewModule -from .modtool_rm import ModToolRemove -from .modtool_rename import ModToolRename -from .templates import Templates -# Leave this at the end -from .parser_cc_block import ParserCCBlock -from .util_functions import * diff --git a/gr-utils/python/modtool/cli/CMakeLists.txt b/gr-utils/python/modtool/cli/CMakeLists.txt new file mode 100644 index 0000000000..07624a3f68 --- /dev/null +++ b/gr-utils/python/modtool/cli/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright 2018 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. + +include(GrPython) + +GR_PYTHON_INSTALL(FILES + __init__.py + add.py + base.py + disable.py + info.py + makeyaml.py + newmod.py + rm.py + rename.py + update.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/cli +) diff --git a/gr-utils/python/modtool/cli/__init__.py b/gr-utils/python/modtool/cli/__init__.py new file mode 100644 index 0000000000..b6751c322c --- /dev/null +++ b/gr-utils/python/modtool/cli/__init__.py @@ -0,0 +1,27 @@ +# +# Copyright 2018 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +from .base import cli, common_params, block_name, ModToolException +from .base import setup_cli_logger, cli_input diff --git a/gr-utils/python/modtool/cli/add.py b/gr-utils/python/modtool/cli/add.py new file mode 100644 index 0000000000..45f749c375 --- /dev/null +++ b/gr-utils/python/modtool/cli/add.py @@ -0,0 +1,133 @@ +# +# Copyright 2018 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. +# +""" Module to add new blocks """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import re + +import click + +from ..core import ModToolAdd +from ..tools import SequenceCompleter, ask_yes_no +from .base import common_params, block_name, run, cli_input, ModToolException + + +@click.command('add') +@click.option('-t', '--block-type', type=click.Choice(ModToolAdd.block_types), + help="One of {}.".format(', '.join(ModToolAdd.block_types))) +@click.option('--license-file', + help="File containing the license header for every source code file.") +@click.option('--copyright', + help="Name of the copyright holder (you or your company) MUST be a quoted string.") +@click.option('--argument-list', default="", + help="The argument list for the constructor and make functions.") +@click.option('--add-python-qa', is_flag=True, default=None, + help="If given, Python QA code is automatically added if possible.") +@click.option('--add-cpp-qa', is_flag=True, default=None, + help="If given, C++ QA code is automatically added if possible.") +@click.option('--skip-cmakefiles', is_flag=True, + help="If given, only source files are written, but CMakeLists.txt files are left unchanged.") +@click.option('-l', '--lang', type=click.Choice(ModToolAdd.language_candidates), + help="Programming Language") +@common_params +@block_name +def cli(**kwargs): + """Adds a block to the out-of-tree module.""" + kwargs['cli'] = True + self = ModToolAdd(**kwargs) + click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green') + get_blocktype(self) + get_lang(self) + click.secho("Language: {}".format({'cpp': 'C++', 'python': 'Python'}[self.info['lang']]), fg='green') + if ((self.skip_subdirs['lib'] and self.info['lang'] == 'cpp') + or (self.skip_subdirs['python'] and self.info['lang'] == 'python')): + raise ModToolException('Missing or skipping relevant subdir.') + get_blockname(self) + click.secho("Block/code identifier: " + self.info['blockname'], fg='green') + self.info['fullblockname'] = self.info['modname'] + '_' + self.info['blockname'] + if not self.license_file: + get_copyrightholder(self) + self.info['license'] = self.setup_choose_license() + get_arglist(self) + get_py_qa(self) + get_cpp_qa(self) + if self.info['version'] == 'autofoo' and not self.skip_cmakefiles: + click.secho("Warning: Autotools modules are not supported. "+ + "Files will be created, but Makefiles will not be edited.", + fg='yellow') + self.skip_cmakefiles = True + run(self) + +def get_blocktype(self): + """ Get the blocktype of the block to be added """ + if self.info['blocktype'] is None: + click.secho(str(self.block_types), fg='yellow') + with SequenceCompleter(self.block_types): + while self.info['blocktype'] not in self.block_types: + self.info['blocktype'] = cli_input("Enter block type: ") + if self.info['blocktype'] not in self.block_types: + click.secho('Must be one of ' + str(self.block_types), fg='yellow') + +def get_lang(self): + """ Get the Programming Language of the block to be added """ + if self.info['lang'] is None: + with SequenceCompleter(self.language_candidates): + while self.info['lang'] not in self.language_candidates: + self.info['lang'] = cli_input("Language (python/cpp): ") + if self.info['lang'] == 'c++': + self.info['lang'] = 'cpp' + +def get_blockname(self): + """ Get the blockname""" + if self.info['blockname'] is None: + while not self.info['blockname'] or self.info['blockname'].isspace(): + self.info['blockname'] = cli_input("Enter name of block/code (without module name prefix): ") + if not re.match('[a-zA-Z0-9_]+', self.info['blockname']): + raise ModToolException('Invalid block name.') + +def get_copyrightholder(self): + """ Get the copyrightholder of the block to be added """ + if self.info['copyrightholder'] is None: + self.info['copyrightholder'] = '<+YOU OR YOUR COMPANY+>' + elif self.info['is_component']: + click.secho("For GNU Radio components the FSF is added as copyright holder", + fg='cyan') + +def get_arglist(self): + """ Get the argument list of the block to be added """ + if self.info['arglist'] is not None: + self.info['arglist'] = cli_input('Enter valid argument list, including default arguments: ') + +def get_py_qa(self): + """ Get a boolean value for addition of py_qa """ + if not (self.info['blocktype'] in ('noblock') or self.skip_subdirs['python']): + if self.add_py_qa is None: + self.add_py_qa = ask_yes_no(click.style('Add Python QA code?', fg='cyan'), True) + +def get_cpp_qa(self): + """ Get a boolean value for addition of cpp_qa """ + if self.info['lang'] == 'cpp': + if self.add_cc_qa is None: + self.add_cc_qa = ask_yes_no(click.style('Add C++ QA code?', fg='cyan'), + not self.add_py_qa) diff --git a/gr-utils/python/modtool/cli/base.py b/gr-utils/python/modtool/cli/base.py new file mode 100644 index 0000000000..5fa2b8b7b4 --- /dev/null +++ b/gr-utils/python/modtool/cli/base.py @@ -0,0 +1,171 @@ +# +# Copyright 2018 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. +# +""" Base CLI module """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import os +import sys +import logging +import functools +from importlib import import_module +from pkg_resources import iter_entry_points +from logging import Formatter, StreamHandler + +import click +from click import ClickException +from click_plugins import with_plugins + +from gnuradio import gr + + +class ModToolException(ClickException): + """ Exception class for enhanced CLI interface """ + def show(self, file = None): + """ displays the colored message """ + click.secho('ModToolException: {}'.format(self.format_message()), fg='red') + + +class CommandCLI(click.Group): + """ + This is a derived class of the implemented click class + which overrides some of the functional definitions for external + plug-in support + """ + cmd_folder = os.path.abspath(os.path.dirname(__file__)) + + def list_commands(self, ctx): + """ + Lists all the commands available in the modtool directory + as well as the commands from external plug-ins. + """ + cmds = [] + for filename in os.listdir(self.cmd_folder): + if filename.endswith('.py') and not '_' in filename: + cmds.append(filename[:-3]) + cmds.remove('base') + cmds += self.commands + return sorted(cmds) + + def get_command(self, ctx, cmd_name): + """ + Returns a command object if it exists. The existing in-tree ModTool + command is the priority over the same external plug-in command. + """ + try: + if sys.version_info[0] == 2: + cmd_name = cmd_name.encode('ascii', 'replace') + mod = import_module('gnuradio.modtool.cli.' + cmd_name) + except ImportError: + return self.commands.get(cmd_name) + return mod.cli + + +class ClickHandler(StreamHandler): + """ + This is a derived class of implemented logging class + StreamHandler which overrides some of its functional + definitions to add colors to the stream output + """ + def emit(self, record): + """ Writes message to the stream """ + colormap = { + 'DEBUG': ('white', 'black'), + 'INFO': ('blue', None), + 'WARNING': ('yellow', None), + 'ERROR': ('red', None), + 'CRITICAL': ('white', 'red'), + } + try: + msg = self.format(record) + colors = colormap.get(record.levelname, (None, None)) + fgcolor = colors[0] + bgcolor = colors[1] + click.secho(msg, fg=fgcolor, bg=bgcolor) + self.flush() + except Exception: + self.handleError(record) + + +def setup_cli_logger(logger): + """ Sets up logger for CLI parsing """ + try: + import colorama + stream_handler = ClickHandler() + logger.addHandler(stream_handler) + except ImportError: + stream_handler = logging.StreamHandler() + logger.addHandler(stream_handler) + finally: + logger.setLevel(logging.INFO) + + +def cli_input(msg): + """ Returns enhanced input """ + return input(click.style(msg, fg='cyan')) + + +def common_params(func): + """ Common parameters for various modules""" + @click.option('-d', '--directory', default='.', + help="Base directory of the module. Defaults to the cwd.") + @click.option('--skip-lib', is_flag=True, + help="Don't do anything in the lib/ subdirectory.") + @click.option('--skip-swig', is_flag=True, + help="Don't do anything in the swig/ subdirectory.") + @click.option('--skip-python', is_flag=True, + help="Don't do anything in the python/ subdirectory.") + @click.option('--skip-grc', is_flag=True, + help="Don't do anything in the grc/ subdirectory.") + @click.option('--scm-mode', type=click.Choice(['yes', 'no', 'auto']), + default=gr.prefs().get_string('modtool', 'scm_mode', 'no'), + help="Use source control management [ yes | no | auto ]).") + @click.option('-y', '--yes', is_flag=True, + help="Answer all questions with 'yes'. " + + "This can overwrite and delete your files, so be careful.") + @functools.wraps(func) + def wrapper(*args, **kwargs): + """ Decorator that wraps common options """ + return func(*args, **kwargs) + return wrapper + + +block_name = click.argument('blockname', nargs=1, required=False, metavar="BLOCK_NAME") + + +@with_plugins(iter_entry_points('gnuradio.modtool.cli.plugins')) +@click.command(cls=CommandCLI, + epilog='Manipulate with GNU Radio modules source code tree. ' + + 'Call it without options to run specified command interactively') +def cli(): + """A tool for editing GNU Radio out-of-tree modules.""" + pass + + +def run(module): + """Call the run function of the core modules.""" + try: + module.run() + except ModToolException as err: + click.echo(err, file=sys.stderr) + exit(1) diff --git a/gr-utils/python/modtool/cli/disable.py b/gr-utils/python/modtool/cli/disable.py new file mode 100644 index 0000000000..4756cda4ee --- /dev/null +++ b/gr-utils/python/modtool/cli/disable.py @@ -0,0 +1,52 @@ +# +# Copyright 2018 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. +# +""" Disable blocks module """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import click + +from ..core import get_block_candidates, ModToolDisable +from ..tools import SequenceCompleter +from .base import common_params, block_name, run, cli_input + + +@click.command('disable', short_help=ModToolDisable.description) +@common_params +@block_name +def cli(**kwargs): + """Disable a block (comments out CMake entries for files)""" + kwargs['cli'] = True + self = ModToolDisable(**kwargs) + click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green') + get_pattern(self) + run(self) + +def get_pattern(self): + """ Get the regex pattern for block(s) to be disabled """ + if self.info['pattern'] is None: + block_candidates = get_block_candidates() + with SequenceCompleter(block_candidates): + self.info['pattern'] = cli_input('Which blocks do you want to disable? (Regex): ') + if not self.info['pattern'] or self.info['pattern'].isspace(): + self.info['pattern'] = '.' diff --git a/gr-utils/python/modtool/cli/info.py b/gr-utils/python/modtool/cli/info.py new file mode 100644 index 0000000000..b03902b299 --- /dev/null +++ b/gr-utils/python/modtool/cli/info.py @@ -0,0 +1,42 @@ +# +# Copyright 2018 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. +# +""" Returns information about a module """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import click + +from ..core import ModToolInfo +from .base import common_params, run + + +@click.command('info') +@click.option('--python-readable', is_flag=True, + help="Return the output in a format that's easier to read for Python scripts.") +@click.option('--suggested-dirs', + help="Suggest typical include dirs if nothing better can be detected.") +@common_params +def cli(**kwargs): + """ Return information about a given module """ + self = ModToolInfo(**kwargs) + run(self) diff --git a/gr-utils/python/modtool/cli/makeyaml.py b/gr-utils/python/modtool/cli/makeyaml.py new file mode 100644 index 0000000000..621d444958 --- /dev/null +++ b/gr-utils/python/modtool/cli/makeyaml.py @@ -0,0 +1,57 @@ +# +# Copyright 2018 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. +# +""" Automatically create YAML bindings for GRC from block code """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import click + +from ..core import get_block_candidates, ModToolMakeYAML +from ..tools import SequenceCompleter +from .base import common_params, block_name, run, cli_input + + +@click.command('makeyaml', short_help=ModToolMakeYAML.description) +@common_params +@block_name +def cli(**kwargs): + """ + \b + Make an YAML file for GRC block bindings + + Note: This does not work on python blocks + """ + kwargs['cli'] = True + self = ModToolMakeYAML(**kwargs) + click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green') + get_pattern(self) + run(self) + +def get_pattern(self): + """ Get the regex pattern for block(s) to be parsed """ + if self.info['pattern'] is None: + block_candidates = get_block_candidates() + with SequenceCompleter(block_candidates): + self.info['pattern'] = cli_input('Which blocks do you want to parse? (Regex): ') + if not self.info['pattern'] or self.info['pattern'].isspace(): + self.info['pattern'] = '.' diff --git a/gr-utils/python/modtool/cli/newmod.py b/gr-utils/python/modtool/cli/newmod.py new file mode 100644 index 0000000000..56fb9e0433 --- /dev/null +++ b/gr-utils/python/modtool/cli/newmod.py @@ -0,0 +1,72 @@ +# +# Copyright 2018 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. +# +""" Create a whole new out-of-tree module """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import re +import os + +import click + +from gnuradio import gr +from ..core import ModToolNewModule +from .base import common_params, run, cli_input, ModToolException + + +@click.command('newmod', short_help=ModToolNewModule.description) +@click.option('--srcdir', + help="Source directory for the module template.") +@common_params +@click.argument('module_name', metavar="MODULE-NAME", nargs=1, required=False) +def cli(**kwargs): + """ + \b + Create a new out-of-tree module + + The argument MODULE-NAME is the name of the module to be added. + """ + kwargs['cli'] = True + self = ModToolNewModule(**kwargs) + get_modname(self) + self.dir = os.path.join(self.dir, 'gr-{}'.format(self.info['modname'])) + try: + os.stat(self.dir) + except OSError: + pass # This is what should happen + else: + raise ModToolException('The given directory exists.') + if self.srcdir is None: + self.srcdir = '/usr/local/share/gnuradio/modtool/templates/gr-newmod' + self.srcdir = gr.prefs().get_string('modtool', 'newmod_path', self.srcdir) + if not os.path.isdir(self.srcdir): + raise ModToolException('Could not find gr-newmod source dir.') + run(self) + +def get_modname(self): + """ Get the name of the new module to be added """ + if self.info['modname'] is None: + while not self.info['modname'] or self.info['modname'].isspace(): + self.info['modname'] = cli_input('Name of the new module: ') + if not re.match('[a-zA-Z0-9_]+$', self.info['modname']): + raise ModToolException('Invalid module name.') diff --git a/gr-utils/python/modtool/cli/rename.py b/gr-utils/python/modtool/cli/rename.py new file mode 100644 index 0000000000..d88c30a75d --- /dev/null +++ b/gr-utils/python/modtool/cli/rename.py @@ -0,0 +1,83 @@ +# +# Copyright 2018 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. +# +""" Module to rename blocks """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import re + +import click + +from ..core import get_block_candidates, ModToolRename +from ..tools import SequenceCompleter +from .base import common_params, block_name, run, cli_input, ModToolException + + +@click.command('rename', short_help=ModToolRename.description) +@common_params +@block_name +@click.argument('new-name', metavar="NEW-BLOCK-NAME", nargs=1, required=False) +def cli(**kwargs): + """ + \b + Rename a block inside a module. + + The argument NEW-BLOCK-NAME is the new name of the block. + """ + kwargs['cli'] = True + self = ModToolRename(**kwargs) + click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green') + # first make sure the old block name is provided + get_oldname(self) + click.secho("Block/code to rename identifier: " + self.info['oldname'], fg='green') + self.info['fulloldname'] = self.info['modname'] + '_' + self.info['oldname'] + # now get the new block name + get_newname(self) + click.secho("Block/code identifier: " + self.info['newname'], fg='green') + self.info['fullnewname'] = self.info['modname'] + '_' + self.info['newname'] + run(self) + +def get_oldname(self): + """ Get the old block name to be replaced """ + block_candidates = get_block_candidates() + if self.info['oldname'] is None: + with SequenceCompleter(block_candidates): + while not self.info['oldname'] or self.info['oldname'].isspace(): + self.info['oldname'] = cli_input("Enter name of block/code to rename "+ + "(without module name prefix): ") + if self.info['oldname'] not in block_candidates: + choices = [x for x in block_candidates if self.info['oldname'] in x] + if len(choices) > 0: + click.secho("Suggested alternatives: "+str(choices), fg='yellow') + raise ModToolException("Blockname for renaming does not exists!") + if not re.match('[a-zA-Z0-9_]+', self.info['oldname']): + raise ModToolException('Invalid block name.') + +def get_newname(self): + """ Get the new block name """ + if self.info['newname'] is None: + while not self.info['newname'] or self.info['newname'].isspace(): + self.info['newname'] = cli_input("Enter name of block/code "+ + "(without module name prefix): ") + if not re.match('[a-zA-Z0-9_]+', self.info['newname']): + raise ModToolException('Invalid block name.') diff --git a/gr-utils/python/modtool/cli/rm.py b/gr-utils/python/modtool/cli/rm.py new file mode 100644 index 0000000000..6ba938086d --- /dev/null +++ b/gr-utils/python/modtool/cli/rm.py @@ -0,0 +1,52 @@ +# +# Copyright 2018 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. +# +""" Remove blocks module """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import click + +from ..core import get_block_candidates, ModToolRemove +from ..tools import SequenceCompleter +from .base import common_params, block_name, run, cli_input + + +@click.command('remove', short_help=ModToolRemove.description) +@common_params +@block_name +def cli(**kwargs): + """ Remove block (delete files and remove Makefile entries) """ + kwargs['cli'] = True + self = ModToolRemove(**kwargs) + click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green') + get_pattern(self) + run(self) + +def get_pattern(self): + """ Returns the regex pattern for block(s) to be removed """ + if self.info['pattern'] is None: + block_candidates = get_block_candidates() + with SequenceCompleter(block_candidates): + self.info['pattern'] = cli_input('Which blocks do you want to delete? (Regex): ') + if not self.info['pattern'] or self.info['pattern'].isspace(): + self.info['pattern'] = '.' diff --git a/gr-utils/python/modtool/cli/update.py b/gr-utils/python/modtool/cli/update.py new file mode 100644 index 0000000000..f5da9077ff --- /dev/null +++ b/gr-utils/python/modtool/cli/update.py @@ -0,0 +1,60 @@ +# +# Copyright 2018 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. +# +""" Module to convert XML bindings to YAML bindings """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import click + +from ..core import get_xml_candidates, ModToolUpdate +from ..tools import SequenceCompleter +from .base import block_name, run, cli_input, ModToolException + + +@click.command('update', short_help=ModToolUpdate.description) +@click.option('--complete', is_flag=True, default=None, + help="Convert all the XML bindings to YAML.") +@block_name +def cli(**kwargs): + """ Update the XML bindings to YAML bindings """ + kwargs['cli'] = True + self = ModToolUpdate(**kwargs) + click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green') + get_blockname(self) + run(self) + +def get_blockname(self): + """ Returns the blockname for block to be updated """ + if self.info['complete']: + return + if self.info['blockname'] is None: + block_candidates = get_xml_candidates() + with SequenceCompleter(block_candidates): + self.info['blockname'] = cli_input('Which block do you wish to update? : ') + if not self.info['blockname'] or self.info['blockname'].isspace(): + raise ModToolException('Block name not specified!') + if self.info['blockname'] not in block_candidates: + choices = [x for x in block_candidates if self.info['blockname'] in x] + if len(choices) > 0: + click.secho("Suggested alternatives: "+str(choices), fg='yellow') + raise ModToolException("The XML bindings does not exists!") diff --git a/gr-utils/python/modtool/core/CMakeLists.txt b/gr-utils/python/modtool/core/CMakeLists.txt new file mode 100644 index 0000000000..e05057be19 --- /dev/null +++ b/gr-utils/python/modtool/core/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright 2011, 2018 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. + +include(GrPython) + +GR_PYTHON_INSTALL(FILES + __init__.py + add.py + base.py + disable.py + info.py + makeyaml.py + newmod.py + rm.py + rename.py + update.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/core +) diff --git a/gr-utils/python/modtool/core/__init__.py b/gr-utils/python/modtool/core/__init__.py new file mode 100644 index 0000000000..eb9386cdf6 --- /dev/null +++ b/gr-utils/python/modtool/core/__init__.py @@ -0,0 +1,34 @@ +# +# Copyright 2013-2014, 2018 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +from .base import ModTool, ModToolException, get_block_candidates +from .add import ModToolAdd +from .disable import ModToolDisable +from .info import ModToolInfo +from .makeyaml import ModToolMakeYAML +from .newmod import ModToolNewModule +from .rm import ModToolRemove +from .rename import ModToolRename +from .update import ModToolUpdate, get_xml_candidates diff --git a/gr-utils/python/modtool/core/add.py b/gr-utils/python/modtool/core/add.py new file mode 100644 index 0000000000..c1a5433831 --- /dev/null +++ b/gr-utils/python/modtool/core/add.py @@ -0,0 +1,301 @@ +# +# Copyright 2013-2014,2017,2018 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. +# +""" Module to add new blocks """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import os +import re +import logging + +from ..tools import render_template, append_re_line_sequence, CMakeFileEditor +from ..templates import Templates +from .base import ModTool, ModToolException + +logger = logging.getLogger(__name__) + + +class ModToolAdd(ModTool): + """ Add block to the out-of-tree module. """ + name = 'add' + description = 'Add new block into a module.' + block_types = ('sink', 'source', 'sync', 'decimator', 'interpolator', + 'general', 'tagged_stream', 'hier', 'noblock') + language_candidates = ('cpp', 'python', 'c++') + + def __init__(self, blockname=None, block_type=None, lang=None, copyright=None, + license_file=None, argument_list="", add_python_qa=False, + add_cpp_qa=False, skip_cmakefiles=False, **kwargs): + ModTool.__init__(self, blockname, **kwargs) + self.info['blocktype'] = block_type + self.info['lang'] = lang + self.license_file = license_file + self.info['copyrightholder'] = copyright + self.info['arglist'] = argument_list + self.add_py_qa = add_python_qa + self.add_cc_qa = add_cpp_qa + self.skip_cmakefiles = skip_cmakefiles + + def validate(self): + """ Validates the arguments """ + ModTool._validate(self) + if self.info['blocktype'] is None: + raise ModToolException('Blocktype not specified.') + if self.info['blocktype'] not in self.block_types: + raise ModToolException('Invalid blocktype') + if self.info['lang'] is None: + raise ModToolException('Programming language not specified.') + if self.info['lang'] not in self.language_candidates: + raise ModToolException('Invalid programming language.') + if self.info['blockname'] is None: + raise ModToolException('Blockname not specified.') + if not re.match('[a-zA-Z0-9_]+', self.info['blockname']): + raise ModToolException('Invalid block name.') + if not isinstance(self.add_py_qa, bool): + raise ModToolException('Expected a boolean value for add_python_qa.') + if not isinstance(self.add_cc_qa, bool): + raise ModToolException('Expected a boolean value for add_cpp_qa.') + if not isinstance(self.skip_cmakefiles, bool): + raise ModToolException('Expected a boolean value for skip_cmakefiles.') + + def assign(self): + if self.info['lang'] == 'c++': + self.info['lang'] = 'cpp' + if ((self.skip_subdirs['lib'] and self.info['lang'] == 'cpp') + or (self.skip_subdirs['python'] and self.info['lang'] == 'python')): + raise ModToolException('Missing or skipping relevant subdir.') + self.info['fullblockname'] = self.info['modname'] + '_' + self.info['blockname'] + if not self.license_file: + if self.info['copyrightholder'] is None: + self.info['copyrightholder'] = '<+YOU OR YOUR COMPANY+>' + self.info['license'] = self.setup_choose_license() + if (self.info['blocktype'] in ('noblock') or self.skip_subdirs['python']): + self.add_py_qa = False + if not self.info['lang'] == 'cpp': + self.add_cc_qa = False + if self.info['version'] == 'autofoo' and not self.skip_cmakefiles: + self.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.license_file is not None \ + and os.path.isfile(self.license_file): + with open(self.license_file) as f: + return f.read() + elif os.path.isfile('LICENSE'): + with open('LICENSE') as f: + return f.read() + elif os.path.isfile('LICENCE'): + with open('LICENCE') as f: + return f.read() + elif self.info['is_component']: + return Templates['grlicense'] + else: + return Templates['defaultlicense'].format(**self.info) + + def _write_tpl(self, tpl, path, fname): + """ Shorthand for writing a substituted template to a file""" + path_to_file = os.path.join(path, fname) + logger.info("Adding file '{}'...".format(path_to_file)) + with open(path_to_file, 'w') as f: + f.write(render_template(tpl, **self.info)) + self.scm.add_files((path_to_file,)) + + def run(self): + """ Go, go, go. """ + # This portion will be covered by the CLI + if not self.cli: + self.validate() + self.assign() + + has_swig = ( + self.info['lang'] == 'cpp' + and not self.skip_subdirs['swig'] + ) + has_grc = False + if self.info['lang'] == 'cpp': + self._run_lib() + has_grc = has_swig + else: # Python + self._run_python() + if self.info['blocktype'] != 'noblock': + has_grc = True + if has_swig: + self._run_swig() + if self.add_py_qa: + self._run_python_qa() + if has_grc and not self.skip_subdirs['grc']: + self._run_grc() + + def _run_cc_qa(self): + " Add C++ QA files for 3.7 API if intructed from _run_lib" + fname_qa_h = 'qa_{}.h'.format(self.info['blockname']) + fname_qa_cc = 'qa_{}.cc'.format(self.info['blockname']) + self._write_tpl('qa_cpp', 'lib', fname_qa_cc) + self._write_tpl('qa_h', 'lib', fname_qa_h) + if self.skip_cmakefiles: + return + 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 "{}"'.format(fname_qa_h)) + append_re_line_sequence(self._file['qalib'], + '(addTest.*suite.*\n|new CppUnit.*TestSuite.*\n)', + ' s->addTest(gr::{}::qa_{}::suite());'.format(self.info['modname'], + self.info['blockname']) + ) + self.scm.mark_files_updated((self._file['qalib'],)) + except IOError: + logger.warning("Can't add C++ QA files.") + + 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 + """ + fname_cc = None + fname_h = None + if self.info['version'] in ('37', '38'): + fname_h = self.info['blockname'] + '.h' + fname_cc = self.info['blockname'] + '.cc' + if self.info['blocktype'] in ('source', 'sink', 'sync', 'decimator', + 'interpolator', 'general', 'hier', 'tagged_stream'): + 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 self.add_cc_qa: + if self.info['version'] == '37': + self._run_cc_qa() + elif self.info['version'] == '36': + logger.warning("Warning: C++ QA files not supported for 3.6-style OOTs.") + elif self.info['version'] == 'autofoo': + logger.warning("Warning: C++ QA files not supported for autotools.") + if not self.skip_cmakefiles: + ed = CMakeFileEditor(self._file['cmlib']) + cmake_list_var = '[a-z]*_?' + self.info['modname'] + '_sources' + if not ed.append_value('list', fname_cc, to_ignore_start='APPEND ' + cmake_list_var): + ed.append_value('add_library', fname_cc) + ed.write() + ed = CMakeFileEditor(self._file['cminclude']) + ed.append_value('install', fname_h, to_ignore_end='DESTINATION[^()]+') + ed.write() + self.scm.mark_files_updated((self._file['cminclude'], self._file['cmlib'])) + + def _run_swig(self): + """ Do everything that needs doing in the subdir 'swig'. + - Edit main *.i file + """ + if self._get_mainswigfile() is None: + logger.warning('Warning: No main swig file found.') + return + logger.info("Editing {}...".format(self._file['swig'])) + mod_block_sep = '/' + if self.info['version'] == '36': + mod_block_sep = '_' + swig_block_magic_str = render_template('swig_block_magic', **self.info) + with open(self._file['swig'], 'a') as f: + f.write(swig_block_magic_str) + include_str = '#include "{}{}{}.h"'.format( + {True: 'gnuradio/' + self.info['modname'], False: self.info['modname']}[self.info['is_component']], + mod_block_sep, + self.info['blockname']) + with open(self._file['swig'], 'r') as f: + oldfile = f.read() + if re.search('#include', oldfile): + append_re_line_sequence(self._file['swig'], '^#include.*\n', include_str) + else: # I.e., if the swig file is empty + regexp = re.compile('^%\{\n', re.MULTILINE) + oldfile = regexp.sub('%%{\n%s\n' % include_str, oldfile, count=1) + with open(self._file['swig'], 'w') as f: + f.write(oldfile) + self.scm.mark_files_updated((self._file['swig'],)) + + 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', self.info['pydir'], fname_py_qa) + os.chmod(os.path.join(self.info['pydir'], fname_py_qa), 0o755) + self.scm.mark_files_updated((os.path.join(self.info['pydir'], fname_py_qa),)) + if self.skip_cmakefiles or CMakeFileEditor(self._file['cmpython']).check_for_glob('qa_*.py'): + return + logger.info("Editing {}/CMakeLists.txt...".format(self.info['pydir'])) + with open(self._file['cmpython'], 'a') as f: + f.write( + 'GR_ADD_TEST(qa_%s ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/%s)\n' % \ + (self.info['blockname'], fname_py_qa)) + self.scm.mark_files_updated((self._file['cmpython'],)) + + 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', self.info['pydir'], fname_py) + append_re_line_sequence(self._file['pyinit'], + '(^from.*import.*\n|# import any pure.*\n)', + 'from .{} import {}'.format(self.info['blockname'], self.info['blockname'])) + self.scm.mark_files_updated((self._file['pyinit'],)) + if self.skip_cmakefiles: + return + ed = CMakeFileEditor(self._file['cmpython']) + ed.append_value('GR_PYTHON_INSTALL', fname_py, to_ignore_end='DESTINATION[^()]+') + ed.write() + self.scm.mark_files_updated((self._file['cmpython'],)) + + def _run_grc(self): + """ Do everything that needs doing in the subdir 'grc' to add + a GRC bindings YAML file. + - add .yml file + - include in CMakeLists.txt + """ + fname_grc = self.info['fullblockname'] + '.block.yml' + self._write_tpl('grc_yml', 'grc', fname_grc) + ed = CMakeFileEditor(self._file['cmgrc'], '\n ') + if self.skip_cmakefiles or ed.check_for_glob('*.yml'): + return + logger.info("Editing grc/CMakeLists.txt...") + ed.append_value('install', fname_grc, to_ignore_end='DESTINATION[^()]+') + ed.write() + self.scm.mark_files_updated((self._file['cmgrc'],)) diff --git a/gr-utils/python/modtool/core/base.py b/gr-utils/python/modtool/core/base.py new file mode 100644 index 0000000000..5d133892e6 --- /dev/null +++ b/gr-utils/python/modtool/core/base.py @@ -0,0 +1,214 @@ +# +# Copyright 2013, 2018 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. +# +""" Base class for the modules """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import os +import re +import glob +import logging +import itertools +from types import SimpleNamespace + +from gnuradio import gr +from ..tools import get_modname, SCMRepoFactory +from ..cli import setup_cli_logger + +logger = logging.getLogger('gnuradio.modtool') + + +def get_block_candidates(): + """ Returns a list of all possible blocknames """ + block_candidates = [] + cpp_filters = ["*.cc", "*.cpp"] + cpp_blocks = [] + for ftr in cpp_filters: + cpp_blocks += [x for x in glob.glob1("lib", ftr) if not (x.startswith('qa_') or + x.startswith('test_'))] + python_blocks = [x for x in glob.glob1("python", "*.py") if not (x.startswith('qa_') or + x.startswith('build') or x.startswith('__init__'))] + for block in itertools.chain(cpp_blocks, python_blocks): + block = os.path.splitext(block)[0] + block = block.split('_impl')[0] + block_candidates.append(block) + return block_candidates + + +class ModToolException(Exception): + """ Standard exception for modtool classes. """ + pass + + +class ModTool(object): + """ Base class for all modtool command classes. """ + name = 'base' + description = None + + def __init__(self, blockname=None, module_name=None, **kwargs): + # List subdirs where stuff happens + self._subdirs = ['lib', 'include', 'python', 'swig', 'grc'] + 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.info['blockname'] = blockname + self.info['modname'] = module_name + self.cli = kwargs.get('cli', False) + self.dir = kwargs.get('directory', '.') + self.skip_subdirs['lib'] = kwargs.get('skip_lib', False) + self.skip_subdirs['python'] = kwargs.get('skip_python', False) + self.skip_subdirs['swig'] = kwargs.get('skip_swig', False) + self.skip_subdirs['grc'] = kwargs.get('skip_grc', False) + self._scm = kwargs.get('scm_mode', + gr.prefs().get_string('modtool', 'scm_mode', 'no')) + if not self.cli: + logging.basicConfig(level=logging.ERROR, format='%(message)s') + self.info['yes'] = True + else: + self.info['yes'] = kwargs.get('yes', False) + setup_cli_logger(logger) + + if not type(self).__name__ in ['ModToolInfo', 'ModToolNewModule']: + if self.cli: + self._validate() + + def _validate(self): + """ Validates the arguments """ + if not isinstance(self.skip_subdirs['lib'], bool): + raise ModToolException('Expected a boolean value for skip_lib') + if not isinstance(self.skip_subdirs['swig'], bool): + raise ModToolException('Expected a boolean value for skip_swig') + if not isinstance(self.skip_subdirs['python'], bool): + raise ModToolException('Expected a boolean value for skip_python') + if not isinstance(self.skip_subdirs['grc'], bool): + raise ModToolException('Expected a boolean value for skip_grc') + self._assign() + + def _assign(self): + if not self._check_directory(self.dir): + raise ModToolException('No GNU Radio module found in the given directory.') + if self.info['modname'] is None: + self.info['modname'] = get_modname() + if self.info['modname'] is None: + raise ModToolException('No GNU Radio module found in the given directory.') + if self.info['version'] == '36' and ( + os.path.isdir(os.path.join('include', self.info['modname'])) or + os.path.isdir(os.path.join('include', 'gnuradio', self.info['modname'])) + ): + self.info['version'] = '37' + if not os.path.isfile(os.path.join('cmake', 'Modules', 'FindCppUnit.cmake')): + self.info['version'] = '38' + if self.skip_subdirs['lib'] or not self.has_subdirs['lib']: + self.skip_subdirs['lib'] = True + if not self.has_subdirs['python']: + self.skip_subdirs['python'] = True + if self._get_mainswigfile() is None or not self.has_subdirs['swig']: + self.skip_subdirs['swig'] = True + if not self.has_subdirs['grc']: + self.skip_subdirs['grc'] = True + + self._setup_files() + self._setup_scm() + + 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.info['pydir'] = 'python' + if os.path.isdir(os.path.join('python', self.info['modname'])): + self.info['pydir'] = os.path.join('python', self.info['modname']) + self._file['qalib'] = os.path.join('lib', 'qa_{}.cc'.format(self.info['modname'])) + self._file['pyinit'] = os.path.join(self.info['pydir'], '__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(self.info['pydir'], 'CMakeLists.txt') + if self.info['is_component']: + self.info['includedir'] = os.path.join('include', 'gnuradio', self.info['modname']) + elif self.info['version'] in ('37', '38'): + 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') + self._file['cmfind'] = os.path.join('cmake', 'Modules', 'howtoConfig.cmake') + + + def _setup_scm(self, mode='active'): + """ Initialize source control management. """ + self.options = SimpleNamespace(scm_mode = self._scm) + if mode == 'active': + self.scm = SCMRepoFactory(self.options, '.').make_active_scm_manager() + else: + self.scm = SCMRepoFactory(self.options, '.').make_empty_scm_manager() + if self.scm is None: + logger.error("Error: Can't set up SCM.") + exit(1) + + 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: + logger.error("Can't read or chdir to directory {}.".format(directory)) + return False + self.info['is_component'] = False + for f in files: + if os.path.isfile(f) and f == 'CMakeLists.txt': + with open(f) as filetext: + if re.search('find_package\(Gnuradio', filetext.read()) is not None: + self.info['version'] = '36' # Might be 37, check that later + has_makefile = True + elif re.search('GR_REGISTER_COMPONENT', filetext.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 list(self.has_subdirs.keys())): + self.has_subdirs[f] = True + else: + self.skip_subdirs[f] = True + return bool(has_makefile and (list(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. """ + raise NotImplementedError('Module implementation missing') diff --git a/gr-utils/python/modtool/modtool_disable.py b/gr-utils/python/modtool/core/disable.py index ed55389fb7..ad1f78ef91 100644 --- a/gr-utils/python/modtool/modtool_disable.py +++ b/gr-utils/python/modtool/core/disable.py @@ -1,5 +1,5 @@ # -# Copyright 2013 Free Software Foundation, Inc. +# Copyright 2013, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -27,9 +27,13 @@ from __future__ import unicode_literals import os import re import sys +import logging -from .modtool_base import ModTool -from .cmakefile_editor import CMakeFileEditor +from ..cli import cli_input +from ..tools import CMakeFileEditor +from .base import ModTool, ModToolException + +logger = logging.getLogger(__name__) class ModToolDisable(ModTool): @@ -37,24 +41,17 @@ class ModToolDisable(ModTool): name = 'disable' description = 'Disable selected block in module.' - def __init__(self): - ModTool.__init__(self) - - @staticmethod - def setup_parser(parser): - ModTool.setup_parser_block(parser) + def __init__(self, blockname=None, **kwargs): + ModTool.__init__(self, blockname, **kwargs) + self.info['pattern'] = blockname - def setup(self, options): - ModTool.setup(self, options) + def validate(self): + """ Validates the arguments """ + ModTool._validate(self) + if not self.info['pattern'] or self.info['pattern'].isspace(): + raise ModToolException("Invalid pattern!") - if options.blockname is not None: - self._info['pattern'] = options.blockname - else: - self._info['pattern'] = input('Which blocks do you want to disable? (Regex): ') - if len(self._info['pattern']) == 0: - self._info['pattern'] = '.' - - def run(self, options): + def run(self): """ Go, go, go! """ def _handle_py_qa(cmake, fname): """ Do stuff for py qa """ @@ -64,29 +61,31 @@ class ModToolDisable(ModTool): def _handle_py_mod(cmake, fname): """ Do stuff for py extra files """ try: - initfile = open(self._file['pyinit']).read() + with open(self._file['pyinit']) as f: + initfile = f.read() except IOError: - print("Could not edit __init__.py, that might be a problem.") + logger.warning("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) + with open(self._file['pyinit'], 'w') as f: + f.write(initfile) self.scm.mark_file_updated(self._file['pyinit']) return False def _handle_cc_qa(cmake, fname): """ Do stuff for cc qa """ - if self._info['version'] == '37': + if self.info['version'] == '37': cmake.comment_out_lines(r'\$\{CMAKE_CURRENT_SOURCE_DIR\}/'+fname) fname_base = os.path.splitext(fname)[0] ed = CMakeFileEditor(self._file['qalib']) # Abusing the CMakeFileEditor... - ed.comment_out_lines(r'#include\s+"%s.h"' % fname_base, comment_str='//') - ed.comment_out_lines(r'%s::suite\(\)' % fname_base, comment_str='//') + ed.comment_out_lines(r'#include\s+"{}.h"'.format(fname_base), comment_str='//') + ed.comment_out_lines(r'{}::suite\(\)'.format(fname_base), comment_str='//') ed.write() self.scm.mark_file_updated(self._file['qalib']) - elif self._info['version'] == '38': + elif self.info['version'] == '38': fname_qa_cc = 'qa_{}.cc'.format(self._info['blockname']) cmake.comment_out_lines(fname_qa_cc) - elif self._info['version'] == '36': + 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]) @@ -95,60 +94,68 @@ class ModToolDisable(ModTool): 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(r'(.include\s+"(%s/)?%s")' % ( - self._info['modname'], fname), r'//\1', swigfile) + with open(self._file['swig']) as f: + swigfile = f.read() + (swigfile, nsubs) = re.subn('(.include\s+"({}/)?{}")'.format( + self.info['modname'], fname), + r'//\1', swigfile) if nsubs > 0: - print("Changing %s..." % self._file['swig']) + logger.info("Changing {}...".format(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'] in ('37', '38'): + blockname = os.path.splitext(fname[len(self.info['modname'])+1:])[0] + if self.info['version'] in ('37', '38'): blockname = os.path.splitext(fname)[0] - (swigfile, nsubs) = re.subn('(GR_SWIG_BLOCK_MAGIC2?.+%s.+;)' % blockname, r'//\1', swigfile) + (swigfile, nsubs) = re.subn('(GR_SWIG_BLOCK_MAGIC2?.+{}.+;)'.format(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) + logger.warning("Hm, changed more then expected while editing {}.".format(self._file['swig'])) + with open(self._file['swig'], 'w') as f: + f.write(swigfile) self.scm.mark_file_updated(self._file['swig']) 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'] in ('37', '38'): + with open(self._file['swig']) as f: + swigfile = f.read() + blockname = os.path.splitext(fname[len(self.info['modname'])+1:])[0] + if self.info['version'] in ('37', '38'): blockname = os.path.splitext(fname)[0] - swigfile = re.sub(r'(%include\s+"'+fname+r'")', r'//\1', swigfile) - print("Changing %s..." % self._file['swig']) + swigfile = re.sub(r'(%include\s+"'+fname+'")', r'//\1', swigfile) + logger.info("Changing {}...".format(self._file['swig'])) swigfile = re.sub('(GR_SWIG_BLOCK_MAGIC2?.+'+blockname+'.+;)', r'//\1', swigfile) - open(self._file['swig'], 'w').write(swigfile) + with open(self._file['swig'], 'w') as f: + f.write(swigfile) self.scm.mark_file_updated(self._file['swig']) return False - self.setup(options) + + # This portion will be covered by the CLI + if not self.cli: + self.validate() # List of special rules: 0: subdir, 1: filename re match, 2: callback special_treatments = ( - ('python', r'qa.+py$', _handle_py_qa), - ('python', r'^(?!qa).+py$', _handle_py_mod), - ('lib', r'qa.+\.cc$', _handle_cc_qa), - ('include/%s' % self._info['modname'], r'.+\.h$', _handle_h_swig), - ('include', r'.+\.h$', _handle_h_swig), - ('swig', r'.+\.i$', _handle_i_swig) + ('python', r'qa.+py$', _handle_py_qa), + ('python', r'^(?!qa).+py$', _handle_py_mod), + ('lib', r'qa.+\.cc$', _handle_cc_qa), + ('include/{}'.format(self.info['modname']), r'.+\.h$', _handle_h_swig), + ('include', r'.+\.h$', _handle_h_swig), + ('swig', r'.+\.i$', _handle_i_swig) ) for subdir in self._subdirs: - if self._skip_subdirs[subdir]: + if self.skip_subdirs[subdir]: continue - if self._info['version'] in ('37', '38') and subdir == 'include': - subdir = 'include/%s' % self._info['modname'] + if self.info['version'] in ('37', '38') and subdir == 'include': + subdir = 'include/{}'.format(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'] + logger.info("Traversing {}...".format(subdir)) + filenames = cmake.find_filenames_match(self.info['pattern']) + yes = self.info['yes'] for fname in filenames: file_disabled = False if not yes: - ans = input("Really disable %s? [Y/n/a/q]: " % fname).lower().strip() + ans = cli_input("Really disable {}? [Y/n/a/q]: ".format(fname)).lower().strip() if ans == 'a': yes = True if ans == 'q': @@ -162,4 +169,4 @@ class ModToolDisable(ModTool): cmake.disable_file(fname) cmake.write() self.scm.mark_files_updated((os.path.join(subdir, 'CMakeLists.txt'),)) - print("Careful: 'gr_modtool disable' does not resolve dependencies.") + logger.warning("Careful: 'gr_modtool disable' does not resolve dependencies.") diff --git a/gr-utils/python/modtool/modtool_info.py b/gr-utils/python/modtool/core/info.py index 2952a9c572..488445df47 100644 --- a/gr-utils/python/modtool/modtool_info.py +++ b/gr-utils/python/modtool/core/info.py @@ -1,4 +1,5 @@ -# Copyright 2013 Free Software Foundation, Inc. +# +# Copyright 2013, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -25,40 +26,24 @@ from __future__ import unicode_literals import os -from .modtool_base import ModTool, ModToolException -from .util_functions import get_modname +from ..tools import get_modname +from .base import ModTool, ModToolException class ModToolInfo(ModTool): """ Return information about a given module """ name = 'info' - description = 'Return informations about module.' - - def __init__(self): - ModTool.__init__(self) - self._directory = None - self._python_readable = False - self._suggested_dirs = None - - @staticmethod - def setup_parser(parser): - """ Initialise the option parser for 'gr_modtool info' """ - #args_group = parser.add_argument_group(title="Info options") - parser.add_argument("--python-readable", action="store_true", - help="Return the output in a format that's easier to read for Python scripts.") - parser.add_argument("--suggested-dirs", - help="Suggest typical include dirs if nothing better can be detected.") - return parser + description = 'Return information about a given module.' - def setup(self, options): - # Won't call parent's setup(), because that's too chatty - self._directory = options.directory - self._python_readable = options.python_readable - self._suggested_dirs = options.suggested_dirs + def __init__(self, python_readable=False, suggested_dirs=None, **kwargs): + ModTool.__init__(self, **kwargs) + # Don't call ModTool._validate(), is is too chatty! + self._directory = self.dir + self._python_readable = python_readable + self._suggested_dirs = suggested_dirs - def run(self, options): + def run(self): """ Go, go, go! """ - self.setup(options) mod_info = dict() mod_info['base_dir'] = self._get_base_dir(self._directory) if mod_info['base_dir'] is None: @@ -67,13 +52,13 @@ class ModToolInfo(ModTool): mod_info['modname'] = get_modname() if mod_info['modname'] is None: raise ModToolException('{}' if self._python_readable else "No module found.") - if self._info['version'] == '36' and ( + if self.info['version'] == '36' and ( os.path.isdir(os.path.join('include', mod_info['modname'])) or os.path.isdir(os.path.join('include', 'gnuradio', mod_info['modname'])) - ): - self._info['version'] = '37' - mod_info['version'] = self._info['version'] - if 'is_component' in list(self._info.keys()) and self._info['is_component']: + ): + self.info['version'] = '37' + mod_info['version'] = self.info['version'] + if 'is_component' in list(self.info.keys()) and self.info['is_component']: mod_info['is_component'] = True mod_info['incdirs'] = [] mod_incl_dir = os.path.join(mod_info['base_dir'], 'include') @@ -130,9 +115,8 @@ class ModToolInfo(ModTool): try: cmakecache_fid = open(os.path.join(mod_info['build_dir'], 'CMakeCache.txt')) for line in cmakecache_fid: - if line.find('GNURADIO_RUNTIME_INCLUDE_DIRS:%s' % path_or_internal) != -1: - inc_dirs += line.replace('GNURADIO_RUNTIME_INCLUDE_DIRS:%s=' - % path_or_internal, '').strip().split(';') + if line.find('GNURADIO_RUNTIME_INCLUDE_DIRS:{}'.format(path_or_internal)) != -1: + inc_dirs += line.replace('GNURADIO_RUNTIME_INCLUDE_DIRS:{}='.format(path_or_internal), '').strip().split(';') except IOError: pass if (not inc_dirs or inc_dirs.isspace) and self._suggested_dirs is not None: @@ -148,11 +132,11 @@ class ModToolInfo(ModTool): 'incdirs': 'Include directories'} for key in list(mod_info.keys()): if key == 'version': - print(" API version: %s" % { + print(" API version: {}".format({ '36': 'pre-3.7', '37': 'post-3.7', '38': 'post-3.8', 'autofoo': 'Autotools (pre-3.5)' - }[mod_info['version']]) + }[mod_info['version']])) else: print('%19s: %s' % (index_names[key], mod_info[key])) diff --git a/gr-utils/python/modtool/modtool_makexml.py b/gr-utils/python/modtool/core/makeyaml.py index a6c4110f80..7f80555338 100644 --- a/gr-utils/python/modtool/modtool_makexml.py +++ b/gr-utils/python/modtool/core/makeyaml.py @@ -1,5 +1,5 @@ # -# Copyright 2013 Free Software Foundation, Inc. +# Copyright 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -18,7 +18,7 @@ # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. # -""" Automatically create XML bindings for GRC from block code """ +""" Automatically create YAML bindings for GRC from block code """ from __future__ import print_function from __future__ import absolute_import @@ -27,48 +27,38 @@ from __future__ import unicode_literals import os import re import glob +import logging -from .modtool_base import ModTool, ModToolException -from .parser_cc_block import ParserCCBlock -from .grc_xml_generator import GRCXMLGenerator -from .cmakefile_editor import CMakeFileEditor -from .util_functions import ask_yes_no +from ..tools import ParserCCBlock, CMakeFileEditor, ask_yes_no, GRCYAMLGenerator +from .base import ModTool, ModToolException +logger = logging.getLogger(__name__) -class ModToolMakeXML(ModTool): - """ Make XML file for GRC block bindings """ - name = 'makexml' - description = 'Generate XML files for GRC block bindings.' - def __init__(self): - ModTool.__init__(self) +class ModToolMakeYAML(ModTool): + """ Make YAML file for GRC block bindings """ + name = 'makeyaml' + description = 'Generate YAML files for GRC block bindings.' - @staticmethod - def setup_parser(parser): - """ Initialise the option parser for 'gr_modtool makexml' """ - parser.usage = """%s + def __init__(self, blockname=None, **kwargs): + ModTool.__init__(self, blockname, **kwargs) + self.info['pattern'] = blockname - Note: This does not work on Python blocks! - """ % parser.usage - ModTool.setup_parser_block(parser) + def validate(self): + """ Validates the arguments """ + ModTool._validate(self) + if not self.info['pattern'] or self.info['pattern'].isspace(): + raise ModToolException("Incorrect blockname (Regex)!") - def setup(self, options): - ModTool.setup(self, options) - - if options.blockname is not None: - self._info['pattern'] = options.blockname - else: - self._info['pattern'] = input('Which blocks do you want to parse? (Regex): ') - if len(self._info['pattern']) == 0: - self._info['pattern'] = '.' - - def run(self, options): + def run(self): """ Go, go, go! """ - print("Warning: This is an experimental feature. Don't expect any magic.") - self.setup(options) + # This portion will be covered by the CLI + if not self.cli: + self.validate() + logger.warning("Warning: This is an experimental feature. Don't expect any magic.") # 1) Go through lib/ - if not self._skip_subdirs['lib']: - if self._info['version'] in ('37', '38'): + if not self.skip_subdirs['lib']: + if self.info['version'] in ('37', '38'): files = self._search_files('lib', '*_impl.cc') else: files = self._search_files('lib', '*.cc') @@ -76,61 +66,61 @@ class ModToolMakeXML(ModTool): 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) + self._make_grc_yaml_from_block_data(params, iosig, blockname) # 2) Go through python/ # TODO def _search_files(self, path, path_glob): """ Search for files matching pattern in the given path. """ - files = sorted(glob.glob("%s/%s"% (path, path_glob))) + files = sorted(glob.glob("{}/{}".format(path, path_glob))) files_filt = [] - print("Searching for matching files in %s/:" % path) + logger.info("Searching for matching files in {}/:".format(path)) for f in files: - if re.search(self._info['pattern'], os.path.basename(f)) is not None: + 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.") + logger.info("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. + def _make_grc_yaml_from_block_data(self, params, iosig, blockname): + """ Take the return values from the parser and call the YAML + generator. Also, check the makefile if the .yml file is in there. If necessary, add. """ - fname_xml = '%s_%s.xml' % (self._info['modname'], blockname) - path_to_xml = os.path.join('grc', fname_xml) + fname_yml = '{}_{}.block.yml'.format(self.info['modname'], blockname) + path_to_yml = os.path.join('grc', fname_yml) # Some adaptions for the GRC for inout in ('in', 'out'): if iosig[inout]['max_ports'] == '-1': - iosig[inout]['max_ports'] = '$num_%sputs' % inout - params.append({'key': 'num_%sputs' % inout, + iosig[inout]['max_ports'] = '$num_{}puts'.format(inout) + params.append({'key': 'num_{}puts'.format(inout), 'type': 'int', - 'name': 'Num %sputs' % inout, + 'name': 'Num {}puts'.format(inout), 'default': '2', 'in_constructor': False}) file_exists = False - if os.path.isfile(path_to_xml): - if not self._info['yes']: + if os.path.isfile(path_to_yml): + if not self.info['yes']: if not ask_yes_no('Overwrite existing GRC file?', False): return else: file_exists = True - print("Warning: Overwriting existing GRC file.") - grc_generator = GRCXMLGenerator( - modname=self._info['modname'], - blockname=blockname, - params=params, - iosig=iosig + logger.warning("Warning: Overwriting existing GRC file.") + grc_generator = GRCYAMLGenerator( + modname=self.info['modname'], + blockname=blockname, + params=params, + iosig=iosig ) - grc_generator.save(path_to_xml) + grc_generator.save(path_to_yml) if file_exists: - self.scm.mark_files_updated((path_to_xml,)) + self.scm.mark_files_updated((path_to_yml,)) else: - self.scm.add_files((path_to_xml,)) - if not self._skip_subdirs['grc']: + self.scm.add_files((path_to_yml,)) + 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, to_ignore_end='DESTINATION[^()]+') + if re.search(fname_yml, ed.cfile) is None and not ed.check_for_glob('*.yml'): + logger.info("Adding GRC bindings to grc/CMakeLists.txt...") + ed.append_value('install', fname_yml, to_ignore_end='DESTINATION[^()]+') ed.write() self.scm.mark_files_updated(self._file['cmgrc']) @@ -159,16 +149,16 @@ class ModToolMakeXML(ModTool): """ 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) + blockname = blockname.replace(self.info['modname']+'_', '', 1) return (blockname, fname_h) # Go, go, go - print("Making GRC bindings for %s..." % fname_cc) + logger.info("Making GRC bindings for {}...".format(fname_cc)) (blockname, fname_h) = _get_blockdata(fname_cc) try: parser = ParserCCBlock(fname_cc, - os.path.join(self._info['includedir'], fname_h), + os.path.join(self.info['includedir'], fname_h), blockname, - self._info['version'], + self.info['version'], _type_translate ) except IOError: diff --git a/gr-utils/python/modtool/core/newmod.py b/gr-utils/python/modtool/core/newmod.py new file mode 100644 index 0000000000..fbe1731783 --- /dev/null +++ b/gr-utils/python/modtool/core/newmod.py @@ -0,0 +1,103 @@ +# +# Copyright 2013, 2018 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. +# +""" Create a whole new out-of-tree module """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import shutil +import os +import re +import logging + +from gnuradio import gr +from ..tools import SCMRepoFactory +from .base import ModTool, ModToolException + +logger = logging.getLogger(__name__) + + +class ModToolNewModule(ModTool): + """ Create a new out-of-tree module """ + name = 'newmod' + description = 'Create new empty module, use add to add blocks.' + def __init__(self, module_name=None, srcdir=None, **kwargs): + ModTool.__init__(self, None, module_name, **kwargs) + # Don't call ModTool._validate(), that assumes an existing module. + self.srcdir = srcdir + self.directory = self.dir + + def assign(self): + self.dir = os.path.join(self.directory, 'gr-{}'.format(self.info['modname'])) + if self.srcdir is None: + self.srcdir = '/usr/local/share/gnuradio/modtool/templates/gr-newmod' + self.srcdir = gr.prefs().get_string('modtool', 'newmod_path', self.srcdir) + + def validate(self): + """ Validates the arguments """ + if self.info['modname'] is None: + raise ModToolException('Module name not specified.') + if not re.match('[a-zA-Z0-9_]+$', self.info['modname']): + raise ModToolException('Invalid module name.') + try: + os.stat(self.dir) + except OSError: + pass # This is what should happen + else: + raise ModToolException('The given directory exists.') + if not os.path.isdir(self.srcdir): + raise ModToolException('Could not find gr-newmod source dir.') + + def run(self): + """ + * Copy the example dir recursively + * Open all files, rename howto and HOWTO to the module name + * Rename files and directories that contain the word howto + """ + # This portion will be covered by the CLI + if not self.cli: + self.assign() + self.validate() + self._setup_scm(mode='new') + logger.info("Creating out-of-tree module in {}...".format(self.dir)) + try: + shutil.copytree(self.srcdir, self.dir) + os.chdir(self.dir) + except OSError: + raise ModToolException('Could not create directory {}.'.format(self.dir)) + for root, dirs, files in os.walk('.'): + for filename in files: + f = os.path.join(root, filename) + with open(f, 'r') as filetext: + s = filetext.read() + s = s.replace('howto', self.info['modname']) + s = s.replace('HOWTO', self.info['modname'].upper()) + with open(f, 'w') as filetext: + filetext.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'])) + logger.info("Done.") + if self.scm.init_repo(path_to_repo="."): + logger.info("Created repository... you might want to commit before continuing.") + logger.info("Use 'gr_modtool add' to add a new block to this currently empty module.") diff --git a/gr-utils/python/modtool/modtool_rename.py b/gr-utils/python/modtool/core/rename.py index 60460680e3..8eec0ee8ec 100644 --- a/gr-utils/python/modtool/modtool_rename.py +++ b/gr-utils/python/modtool/core/rename.py @@ -1,5 +1,5 @@ # -# Copyright 2014 Free Software Foundation, Inc. +# Copyright 2014, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -26,13 +26,11 @@ from __future__ import unicode_literals import os import re -import glob +import logging -from .util_functions import append_re_line_sequence, ask_yes_no -from .util_functions import SequenceCompleter -from .cmakefile_editor import CMakeFileEditor -from .modtool_base import ModTool, ModToolException -from .templates import Templates +from .base import get_block_candidates, ModTool, ModToolException + +logger = logging.getLogger(__name__) def get_block_candidates(): cpp_filters = ["*.cc", "*.cpp"] @@ -52,78 +50,61 @@ def get_block_candidates(): class ModToolRename(ModTool): """ Rename a block in the out-of-tree module. """ name = 'rename' - description = 'Rename block inside module.' - - def __init__(self): - ModTool.__init__(self) - self._add_cc_qa = False - self._add_py_qa = False - self._skip_cmakefiles = False - self._license_file = None - - @staticmethod - def setup_parser(parser): - #parser = parser.add_argument_group(title="Rename module options") - ModTool.setup_parser_block(parser) - parser.add_argument("new_name", nargs="?", metavar='NEW-BLOCK-NAME', - help="New name of the block.") - return parser - - def setup(self, options): - ModTool.setup(self, options) - - if ((self._skip_subdirs['lib'] and self._info['lang'] == 'cpp') - or (self._skip_subdirs['python'] and self._info['lang'] == 'python')): - raise ModToolException('Missing or skipping relevant subdir.') - - # first make sure the old block name is provided - self._info['oldname'] = options.blockname - if self._info['oldname'] is None: - self._info['oldname'] = input("Enter name of block/code to rename (without module name prefix): ") - else: - block_candidates = get_block_candidates() - with SequenceCompleter(block_candidates): - self._info['oldname'] = \ - raw_input("Enter name of block/code to rename (without module name prefix): ") - if not re.match('[a-zA-Z0-9_]+', self._info['oldname']): - raise ModToolException('Invalid block name.') - print("Block/code to rename identifier: " + self._info['oldname']) - self._info['fulloldname'] = self._info['modname'] + '_' + self._info['oldname'] - - # now get the new block name - if options.new_name is None: - self._info['newname'] = input("Enter name of block/code (without module name prefix): ") - else: - self._info['newname'] = options.new_name[0] - if not re.match('[a-zA-Z0-9_]+', self._info['newname']): + description = 'Rename a block inside a module.' + + def __init__(self, blockname=None, new_name=None, **kwargs): + ModTool.__init__(self, blockname, **kwargs) + self.info['oldname'] = blockname + self.info['newname'] = new_name + + def validate(self): + """ Validates the arguments """ + ModTool._validate(self) + if not self.info['oldname']: + raise ModToolException('Old block name (blockname) not specified.') + if not re.match('[a-zA-Z0-9_]+', self.info['oldname']): raise ModToolException('Invalid block name.') - print("Block/code identifier: " + self._info['newname']) - self._info['fullnewname'] = self._info['modname'] + '_' + self._info['newname'] - - def run(self, options): + block_candidates = get_block_candidates() + if self.info['oldname'] not in block_candidates: + choices = [x for x in block_candidates if self.info['oldname'] in x] + if len(choices)>0: + print("Suggested alternatives: "+str(choices)) + raise ModToolException("Blockname for renaming does not exists!") + if not self.info['newname']: + raise ModToolException('New blockname (new_name) not specified.') + if not re.match('[a-zA-Z0-9_]+', self.info['newname']): + raise ModToolException('Invalid new block name.') + + def assign(self): + self.info['fullnewname'] = self.info['modname'] + '_' + self.info['newname'] + + def run(self): """ Go, go, go. """ - self.setup(options) - module = self._info['modname'] - oldname = self._info['oldname'] - newname = self._info['newname'] - print("In module '%s' rename block '%s' to '%s'" % (module, oldname, newname)) + # This portion will be covered by the CLI + if not self.cli: + self.validate() + self.assign() + module = self.info['modname'] + oldname = self.info['oldname'] + newname = self.info['newname'] + logger.info("In module '{}' rename block '{}' to '{}'".format(module, oldname, newname)) self._run_swig_rename(self._file['swig'], oldname, newname) - self._run_grc_rename(self._info['modname'], oldname, newname) - self._run_python_qa(self._info['modname'], oldname, newname) - self._run_python(self._info['modname'], oldname, newname) - self._run_lib(self._info['modname'], oldname, newname) - self._run_include(self._info['modname'], oldname, newname) + self._run_grc_rename(self.info['modname'], oldname, newname) + self._run_python_qa(self.info['modname'], oldname, newname) + self._run_python(self.info['modname'], oldname, newname) + self._run_lib(self.info['modname'], oldname, newname) + self._run_include(self.info['modname'], oldname, newname) return def _run_swig_rename(self, swigfilename, old, new): """ Rename SWIG includes and block_magic """ nsubs = self._run_file_replace(swigfilename, old, new) if nsubs < 1: - print("Couldn't find '%s' in file '%s'." % (old, swigfilename)) + logger.info("Couldn't find '{}' in file '{}'.".format(old, swigfilename)) if nsubs == 2: - print("Changing 'noblock' type file") + logger.info("Changing 'noblock' type file") if nsubs > 3: - print("Hm, changed more then expected while editing %s." % swigfilename) + logger.warning("Hm, changed more then expected while editing {}.".format(swigfilename)) return False def _run_lib(self, module, old, new): @@ -143,7 +124,7 @@ class ModToolRename(ModTool): filename = 'qa_' + module + '.cc' nsubs = self._run_file_replace(path + filename, old, new) if nsubs > 0: - print("C++ QA code detected, renaming...") + logger.info("C++ QA code detected, renaming...") filename = 'qa_' + old + '.cc' self._run_file_replace(path + filename, old, new) filename = 'qa_' + old + '.h' @@ -151,7 +132,7 @@ class ModToolRename(ModTool): self._run_file_replace(path + filename, old.upper(), new.upper()) self._run_file_rename(path, 'qa_' + old, 'qa_' + new) else: - print("No C++ QA code detected, skipping...") + logger.info("No C++ QA code detected, skipping...") def _run_include(self, module, old, new): path = './include/' + module + '/' @@ -166,13 +147,13 @@ class ModToolRename(ModTool): filename = '__init__.py' nsubs = self._run_file_replace(path + filename, old, new) if nsubs > 0: - print("Python block detected, renaming...") + logger.info("Python block detected, renaming...") filename = old + '.py' self._run_file_replace(path + filename, old, new) self._run_cmakelists(path, old, new) self._run_file_rename(path, old, new) else: - print("Not a Python block, nothing to do here...") + logger.info("Not a Python block, nothing to do here...") def _run_python_qa(self, module, old, new): new = 'qa_' + new @@ -183,7 +164,7 @@ class ModToolRename(ModTool): self._run_file_rename('./python/', old, new) def _run_grc_rename(self, module, old, new): - grcfile = './grc/' + module + '_' + old + '.xml' + grcfile = './grc/' + module + '_' + old + '.yml' self._run_file_replace(grcfile, old, new) self._run_cmakelists('./grc/', old, new) self._run_file_rename('./grc/', module + '_' + old, module + '_' + new) @@ -192,7 +173,7 @@ class ModToolRename(ModTool): filename = path + 'CMakeLists.txt' nsubs = self._run_file_replace(filename, first, second) if nsubs < 1: - print("'%s' wasn't in '%s'." % (first, filename)) + logger.info("'{}' wasn't in '{}'.".format(first, filename)) def _run_file_rename(self, path, old, new): files = os.listdir(path) @@ -201,18 +182,20 @@ class ModToolRename(ModTool): nl = file.replace(old, new) src = path + file dst = path + nl - print("Renaming file '%s' to '%s'." % (src, dst)) + logger.info("Renaming file '{}' to '{}'.".format(src, dst)) os.rename(src, dst) def _run_file_replace(self, filename, old, new): if not os.path.isfile(filename): return False else: - print("In '%s' renaming occurrences of '%s' to '%s'" % (filename, old, new)) + logger.info("In '{}' renaming occurences of '{}' to '{}'".format(filename, old, new)) - cfile = open(filename).read() + with open(filename) as f: + cfile = f.read() (cfile, nsubs) = re.subn(old, new, cfile) - open(filename, 'w').write(cfile) + with open(filename, 'w') as f: + f.write(cfile) self.scm.mark_file_updated(filename) return nsubs diff --git a/gr-utils/python/modtool/modtool_rm.py b/gr-utils/python/modtool/core/rm.py index 55598a5090..688aa59835 100644 --- a/gr-utils/python/modtool/modtool_rm.py +++ b/gr-utils/python/modtool/core/rm.py @@ -1,5 +1,5 @@ # -# Copyright 2013 Free Software Foundation, Inc. +# Copyright 2013, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -28,57 +28,54 @@ import os import re import sys import glob +import logging -from .util_functions import remove_pattern_from_file, SequenceCompleter -from .modtool_base import ModTool -from .cmakefile_editor import CMakeFileEditor +from ..tools import remove_pattern_from_file, CMakeFileEditor +from ..cli import cli_input +from .base import ModTool, ModToolException + +logger = logging.getLogger(__name__) class ModToolRemove(ModTool): """ Remove block (delete files and remove Makefile entries) """ name = 'remove' - description = 'Remove block from module.' - - def __init__(self): - ModTool.__init__(self) - - @staticmethod - def setup_parser(parser): - ModTool.setup_parser_block(parser) + description = 'Remove a block from a module.' - def setup(self, options): - ModTool.setup(self, options) + def __init__(self, blockname=None, **kwargs): + ModTool.__init__(self, blockname, **kwargs) + self.info['pattern'] = blockname - if options.blockname is not None: - self._info['pattern'] = options.blockname - else: - block_candidates = self.get_block_candidates() - with SequenceCompleter(block_candidates): - self._info['pattern'] = input('Which blocks do you want to delete? (Regex): ') - if not self._info['pattern'] or self._info['pattern'].isspace(): - self._info['pattern'] = '.' + def validate(self): + """ Validates the arguments """ + ModTool._validate(self) + if not self.info['pattern'] or self.info['pattern'].isspace(): + raise ModToolException("Incorrect blockname (Regex)!") - def run(self, options): + def run(self): """ Go, go, go! """ + # This portion will be covered by the CLI + if not self.cli: + self.validate() 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': + if self.info['version'] == '37': (base, ext) = os.path.splitext(filename) if ext == '.h': remove_pattern_from_file(self._file['qalib'], - r'^#include "%s"\s*$' % filename) + r'^#include "{}"\s*$'.format(filename)) remove_pattern_from_file(self._file['qalib'], - r'^\s*s->addTest\(gr::%s::%s::suite\(\)\);\s*$' % ( - self._info['modname'], base) + r'^\s*s->addTest\(gr::{}::{}::suite\(\)\);\s*$'.format( + self.info['modname'], base) ) self.scm.mark_file_updated(self._file['qalib']) elif ext == '.cc': ed.remove_value('list', r'\$\{CMAKE_CURRENT_SOURCE_DIR\}/%s' % filename, - to_ignore_start='APPEND test_%s_sources' % self._info['modname']) + to_ignore_start='APPEND test_{}_sources'.format(self.info['modname'])) self.scm.mark_file_updated(ed.filename) elif self._info['version'] == '38': (base, ext) = os.path.splitext(filename) @@ -106,36 +103,35 @@ class ModToolRemove(ModTool): 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) + pyblockname = filebase.replace(self.info['modname'] + '_', '') + regexp = r'(^\s*GR_SWIG_BLOCK_MAGIC2?\({},\s*{}\);|^\s*.include\s*"({}/)?{}"\s*)'.format \ + (self.info['modname'], pyblockname, self.info['modname'], filename) return regexp # Go, go, go! - self.setup(options) - if not self._skip_subdirs['lib']: + if not self.skip_subdirs['lib']: self._run_subdir('lib', ('*.cc', '*.h'), ('add_library', 'list'), 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']: + 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)) self.scm.mark_file_updated(self._file['swig']) - if not self._skip_subdirs['python']: + 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'], r'.*import\s+%s.*' % f[:-3]) - remove_pattern_from_file(self._file['pyinit'], r'.*from\s+%s\s+import.*\n' % f[:-3]) - if not self._skip_subdirs['grc']: - self._run_subdir('grc', ('*.xml',), ('install',)) + remove_pattern_from_file(self._file['pyinit'], r'.*import\s+{}.*'.format(f[:-3])) + remove_pattern_from_file(self._file['pyinit'], r'.*from\s+{}\s+import.*\n'.format(f[:-3])) + if not self.skip_subdirs['grc']: + self._run_subdir('grc', ('*.yml',), ('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) + globs - A tuple of standard UNIX globs of files to delete (e.g. *.yml) 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 @@ -143,23 +139,23 @@ class ModToolRemove(ModTool): # 1. Create a filtered list files = [] for g in globs: - files = files + sorted(glob.glob("%s/%s"% (path, g))) + files = files + sorted(glob.glob("{}/{}".format(path, g))) files_filt = [] - print("Searching for matching files in %s/:" % path) + logger.info("Searching for matching files in {}/:".format(path)) for f in files: - if re.search(self._info['pattern'], os.path.basename(f)) is not None: + 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.") + logger.info("None found.") return [] # 2. Delete files, Makefile entries and other occurrences files_deleted = [] - ed = CMakeFileEditor('%s/CMakeLists.txt' % path) - yes = self._info['yes'] + ed = CMakeFileEditor('{}/CMakeLists.txt'.format(path)) + yes = self.info['yes'] for f in files_filt: b = os.path.basename(f) if not yes: - ans = input("Really delete %s? [Y/n/a/q]: " % f).lower().strip() + ans = cli_input("Really delete {}? [Y/n/a/q]: ".format(f)).lower().strip() if ans == 'a': yes = True if ans == 'q': @@ -167,14 +163,14 @@ class ModToolRemove(ModTool): if ans == 'n': continue files_deleted.append(b) - print("Deleting %s." % f) + logger.info("Deleting {}.".format(f)) self.scm.remove_file(f) os.unlink(f) - print("Deleting occurrences of %s from %s/CMakeLists.txt..." % (b, path)) + logger.info("Deleting occurrences of {} from {}/CMakeLists.txt...".format(b, path)) for var in makefile_vars: ed.remove_value(var, b) if cmakeedit_func is not None: cmakeedit_func(b, ed) ed.write() - self.scm.mark_files_updated(('%s/CMakeLists.txt' % path,)) + self.scm.mark_files_updated(('{}/CMakeLists.txt'.format(path))) return files_deleted diff --git a/gr-utils/python/modtool/core/update.py b/gr-utils/python/modtool/core/update.py new file mode 100644 index 0000000000..ec8a519d45 --- /dev/null +++ b/gr-utils/python/modtool/core/update.py @@ -0,0 +1,109 @@ +# +# Copyright 2018 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. +# +""" Module to convert XML bindings to YAML bindings """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import os +import re +import glob +import logging + +from gnuradio.grc.converter import Converter +from .base import ModTool, ModToolException + +logger = logging.getLogger(__name__) + + +def get_xml_candidates(): + """ Returns a list of XML candidates for update """ + xml_candidates = [] + xml_files = [x for x in glob.glob1("grc", "*.xml")] + for candidate in xml_files: + candidate = os.path.splitext(candidate)[0] + candidate = candidate.split("_", 1)[-1] + xml_candidates.append(candidate) + return xml_candidates + + +class ModToolUpdate(ModTool): + """ Update the grc bindings for a block """ + name = 'update' + description = 'Update the grc bindings for a block' + + def __init__(self, blockname=None, complete=False, **kwargs): + ModTool.__init__(self, blockname, **kwargs) + self.info['complete'] = complete + + + def validate(self): + """ Validates the arguments """ + ModTool._validate(self) + if self.info['complete']: + return + if not self.info['blockname'] or self.info['blockname'].isspace(): + raise ModToolException('Block name not specified!') + block_candidates = get_xml_candidates() + if self.info['blockname'] not in block_candidates: + choices = [x for x in block_candidates if self.info['blockname'] in x] + if len(choices) > 0: + print("Suggested alternatives: "+str(choices)) + raise ModToolException("The XML bindings does not exists!") + + def run(self): + if not self.cli: + self.validate() + logger.warning("Warning: This is an experimental feature. Please verify the bindings.") + module_name = self.info['modname'] + path = './grc/' + conv = Converter(path, path) + if self.info['complete']: + blocks = get_xml_candidates() + else: + blocks = [self.info['blockname']] + for blockname in blocks: + xml_file = "{}_{}.xml".format(module_name, blockname) + yml_file = "{}_{}.block.yml".format(module_name, blockname) + conv.load_block_xml(path+xml_file) + logger.info("Converted {} to {}".format(xml_file, yml_file)) + os.remove(path+xml_file) + nsubs = self._run_cmakelists(xml_file, yml_file) + if nsubs > 1: + logger.warning("Changed more than expected for the block '%s' in the CMakeLists.txt. " + "Please verify the CMakeLists manually.", blockname) + elif nsubs == 0: + logger.warning("No entry found for the block '%s' in the CMakeLists.txt. " + 'Please verify the CMakeLists manually.', blockname) + else: + logger.info('Updated the CMakeLists.txt') + + def _run_cmakelists(self, to_remove, to_add): + """ Changes in the CMakeLists """ + filename = './grc/CMakeLists.txt' + with open(filename) as f: + cfile = f.read() + (cfile, nsubs) = re.subn(to_remove, to_add, cfile) + with open(filename, 'w') as f: + f.write(cfile) + self.scm.mark_file_updated(filename) + return nsubs diff --git a/gr-utils/python/modtool/grc_xml_generator.py b/gr-utils/python/modtool/grc_xml_generator.py deleted file mode 100644 index 1109701f7e..0000000000 --- a/gr-utils/python/modtool/grc_xml_generator.py +++ /dev/null @@ -1,105 +0,0 @@ -from __future__ import absolute_import -# -# Copyright 2013 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. -# -import xml.etree.ElementTree as ET -from .util_functions import is_number, xml_indent - -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', '[%s]' % 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/python/modtool/modtool_add.py b/gr-utils/python/modtool/modtool_add.py deleted file mode 100644 index 9f128d2fde..0000000000 --- a/gr-utils/python/modtool/modtool_add.py +++ /dev/null @@ -1,367 +0,0 @@ -# -# Copyright 2013, 2017-2018 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. -# -""" Module to add new blocks """ - -from __future__ import print_function -from __future__ import absolute_import -from __future__ import unicode_literals - -from builtins import input - -import os -import re -import getpass - -from .util_functions import append_re_line_sequence, ask_yes_no, SequenceCompleter -from .cmakefile_editor import CMakeFileEditor -from .modtool_base import ModTool, ModToolException -from .templates import Templates -from .code_generator import render_template - -class ModToolAdd(ModTool): - """ Add block to the out-of-tree module. """ - name = 'add' - description = 'Add new block into module.' - _block_types = ('sink', 'source', 'sync', 'decimator', 'interpolator', - 'general', 'tagged_stream', 'hier', 'noblock') - - def __init__(self): - ModTool.__init__(self) - self._add_cc_qa = False - self._add_py_qa = False - self._skip_cmakefiles = False - self._license_file = None - - @staticmethod - def setup_parser(parser): - parser.add_argument("-t", "--block-type", choices=ModToolAdd._block_types, - help="One of %s." % ', '.join(ModToolAdd._block_types)) - parser.add_argument("--license-file", - help="File containing the license header for every source code file.") - parser.add_argument("--copyright", - help="Name of the copyright holder (you or your company) MUST be a quoted string.") - parser.add_argument("--argument-list", - help="The argument list for the constructor and make functions.") - parser.add_argument("--add-python-qa", action="store_true", default=None, - help="If given, Python QA code is automatically added if possible.") - parser.add_argument("--add-cpp-qa", action="store_true", default=None, - help="If given, C++ QA code is automatically added if possible.") - parser.add_argument("--skip-cmakefiles", action="store_true", - help="If given, only source files are written, but CMakeLists.txt files are left unchanged.") - parser.add_argument("-l", "--lang", choices=('cpp', 'c++', 'python'), - help="Programing language") - ModTool.setup_parser_block(parser) - return parser - - def setup(self, options): - ModTool.setup(self, options) - - if self._info['blockname'] is None: - if len(options) >= 2: - self._info['blockname'] = options[1] - else: - self._info['blockname'] = input("Enter name of block/code (without module name prefix): ") - if os.path.isfile("./lib/"+self._info['blockname']+"_impl.cc") or os.path.isfile("./python/"+self._info['blockname']+".py"): - raise ModToolException('The given blockname already exists!') - if not re.match('[a-zA-Z0-9_]+', self._info['blockname']): - raise ModToolException('Invalid block name.') - print("Block/code identifier: " + self._info['blockname']) - self._info['fullblockname'] = self._info['modname'] + '_' + self._info['blockname'] - - self._info['blocktype'] = options.block_type - if self._info['blocktype'] is None: - # Print list out of blocktypes to user for reference - print(str(self._block_types)) - with SequenceCompleter(sorted(self._block_types)): - while self._info['blocktype'] not in self._block_types: - self._info['blocktype'] = input("Enter block type: ") - if self._info['blocktype'] not in self._block_types: - print('Must be one of ' + str(self._block_types)) - - # Allow user to specify language interactively if not set - self._info['lang'] = options.lang - if self._info['lang'] is None: - language_candidates = ('c++', 'cpp', 'python') - with SequenceCompleter(language_candidates): - while self._info['lang'] not in language_candidates: - self._info['lang'] = input("Language (python/cpp): ") - 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')): - raise ModToolException('Missing or skipping relevant subdir.') - - if self._info['blockname'] is None: - self._info['blockname'] = input("Enter name of block/code (without module name prefix): ") - if not re.match('[a-zA-Z0-9_]+', self._info['blockname']): - raise ModToolException('Invalid block name.') - print("Block/code identifier: " + self._info['blockname']) - self._info['fullblockname'] = self._info['modname'] + '_' + self._info['blockname'] - if not options.license_file: - self._info['copyrightholder'] = options.copyright - if self._info['copyrightholder'] is None: - user = getpass.getuser() - git_user = self.scm.get_gituser() - if git_user: - copyright_candidates = (user, git_user, 'GNU Radio') - else: - copyright_candidates = (user, 'GNU Radio') - with SequenceCompleter(copyright_candidates): - self._info['copyrightholder'] = input("Please specify the copyright holder: ") - if not self._info['copyrightholder'] or self._info['copyrightholder'].isspace(): - self._info['copyrightholder'] = "gr-"+self._info['modname']+" author" - elif self._info['is_component']: - print("For GNU Radio components the FSF is added as copyright holder") - self._license_file = options.license_file - self._info['license'] = self.setup_choose_license() - if options.argument_list is not None: - self._info['arglist'] = options.argument_list - else: - self._info['arglist'] = 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) - self._skip_cmakefiles = options.skip_cmakefiles - if self._info['version'] == 'autofoo' and not self._skip_cmakefiles: - print("Warning: Autotools modules are not supported. ", - "Files will be created, but Makefiles will not be edited.") - self._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._license_file is not None \ - and os.path.isfile(self._license_file): - return open(self._license_file).read() - elif os.path.isfile('LICENSE'): - return open('LICENSE').read() - elif os.path.isfile('LICENCE'): - return open('LICENCE').read() - elif self._info['is_component']: - return Templates['grlicense'] - else: - return Templates['defaultlicense'].format(**self._info) - - def _write_tpl(self, tpl, path, fname): - """ Shorthand for writing a substituted template to a file""" - path_to_file = os.path.join(path, fname) - print("Adding file '%s'..." % path_to_file) - open(path_to_file, 'w').write(render_template(tpl, **self._info)) - self.scm.add_files((path_to_file,)) - - def run(self, options): - """ Go, go, go. """ - self.setup(options) - has_swig = ( - self._info['lang'] == 'cpp' - and not self._skip_subdirs['swig'] - ) - has_grc = False - if self._info['lang'] == 'cpp': - self._run_lib() - has_grc = has_swig - else: # Python - self._run_python() - if self._info['blocktype'] != 'noblock': - has_grc = True - if has_swig: - self._run_swig() - if self._add_py_qa: - self._run_python_qa() - if has_grc and not self._skip_subdirs['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._skip_cmakefiles: - try: - append_re_line_sequence(self._file['cmlib'], - r'\$\{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']) - ) - self.scm.mark_files_updated((self._file['qalib'],)) - except IOError: - print("Can't add C++ QA files.") - def _add_qa_boostutf(): - " Add C++ QA files for 3.8 API " - fname_qa_cc = 'qa_%s.cc' % self._info['blockname'] - self._write_tpl('qa_cpp_boostutf', 'lib', fname_qa_cc) - if not self._skip_cmakefiles: - try: - ed = CMakeFileEditor(self._file['cmlib']) - cmake_list_var = \ - 'test_{}_source'.format(self._info['modname']) - if not ed.append_value( - 'list', - fname_qa_cc, - to_ignore_start='APPEND ' + cmake_list_var): - print("Couldn't add C++ QA files.") - ed.write() - self.scm.mark_files_updated((self._file['cmlib'],)) - except IOError: - print("Can't add C++ QA files.") - fname_cc = None - fname_h = None - if self._info['version'] in ('37', '38'): - fname_h = self._info['blockname'] + '.h' - fname_cc = self._info['blockname'] + '.cc' - if self._info['blocktype'] in ('source', 'sink', 'sync', 'decimator', - 'interpolator', 'general', 'hier', 'tagged_stream'): - 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 self._add_cc_qa: - if self._info['version'] == '37': - _add_qa() - if self._info['version'] == '38': - _add_qa_boostutf() - elif self._info['version'] == '36': - print("Warning: C++ QA files not supported for 3.6-style OOTs.") - elif self._info['version'] == 'autofoo': - print("Warning: C++ QA files not supported for autotools.") - if not self._skip_cmakefiles: - ed = CMakeFileEditor(self._file['cmlib']) - cmake_list_var = '[a-z]*_?' + self._info['modname'] + '_sources' - if not ed.append_value('list', fname_cc, to_ignore_start='APPEND ' + cmake_list_var): - ed.append_value('add_library', fname_cc) - ed.write() - ed = CMakeFileEditor(self._file['cminclude']) - ed.append_value('install', fname_h, to_ignore_end='DESTINATION[^()]+') - ed.write() - self.scm.mark_files_updated((self._file['cminclude'], self._file['cmlib'])) - - 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 = render_template('swig_block_magic', **self._info) - open(self._file['swig'], 'a').write(swig_block_magic_str) - include_str = '#include "%s%s%s.h"' % ( - {True: 'gnuradio/' + self._info['modname'], - False: self._info['modname']}[self._info['is_component']], - 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(r'^%\{\n', re.MULTILINE) - oldfile = regexp.sub('%%{\n%s\n' % include_str, oldfile, count=1) - open(self._file['swig'], 'w').write(oldfile) - self.scm.mark_files_updated((self._file['swig'],)) - - 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', self._info['pydir'], fname_py_qa) - os.chmod(os.path.join(self._info['pydir'], fname_py_qa), 0o755) - self.scm.mark_files_updated((os.path.join(self._info['pydir'], fname_py_qa),)) - if self._skip_cmakefiles or CMakeFileEditor(self._file['cmpython']).check_for_glob('qa_*.py'): - return - print("Editing %s/CMakeLists.txt..." % self._info['pydir']) - 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)) - self.scm.mark_files_updated((self._file['cmpython'],)) - - 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', self._info['pydir'], fname_py) - append_re_line_sequence(self._file['pyinit'], - '(^from.*import.*\n|# import any pure.*\n)', - 'from %s import %s' % (self._info['blockname'], - self._info['blockname'])) - self.scm.mark_files_updated((self._file['pyinit'],)) - if self._skip_cmakefiles: - return - ed = CMakeFileEditor(self._file['cmpython']) - ed.append_value('GR_PYTHON_INSTALL', fname_py, to_ignore_end='DESTINATION[^()]+') - ed.write() - self.scm.mark_files_updated((self._file['cmpython'],)) - - 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._skip_cmakefiles or ed.check_for_glob('*.xml'): - return - print("Editing grc/CMakeLists.txt...") - ed.append_value('install', fname_grc, to_ignore_end='DESTINATION[^()]+') - ed.write() - self.scm.mark_files_updated((self._file['cmgrc'],)) diff --git a/gr-utils/python/modtool/modtool_base.py b/gr-utils/python/modtool/modtool_base.py deleted file mode 100644 index 41d83ccafb..0000000000 --- a/gr-utils/python/modtool/modtool_base.py +++ /dev/null @@ -1,212 +0,0 @@ -# -# Copyright 2013,2018 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. -# -""" Base class for the modules """ - -from __future__ import print_function -from __future__ import absolute_import -from __future__ import unicode_literals - -import os -import re -from argparse import ArgumentParser, RawDescriptionHelpFormatter - -from gnuradio import gr -from .util_functions import get_modname -from .scm import SCMRepoFactory - -class ModToolException(BaseException): - """ Standard exception for modtool classes. """ - pass - -class ModTool(object): - """ Base class for all modtool command classes. """ - name = 'base' - description = None - - 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._dir = None - self.scm = None - - @staticmethod - def setup_parser(parser): - """Override in child class.""" - pass - - @staticmethod - def setup_parser_block(parser): - """Setup options specific for block manipulating modules.""" - parser.add_argument("blockname", nargs="?", metavar="BLOCK_NAME", - help="Name of the block/module") - - @staticmethod - def get_parser(): - """Init the option parser.""" - parser = ArgumentParser( - description='Manipulate with GNU Radio modules source code tree. ' + \ - 'Call it withou options to run specified command interactively', - formatter_class=RawDescriptionHelpFormatter) - parser.add_argument("-d", "--directory", default=".", - help="Base directory of the module. Defaults to the cwd.") - parser.add_argument("--skip-lib", action="store_true", - help="Don't do anything in the lib/ subdirectory.") - parser.add_argument("--skip-swig", action="store_true", - help="Don't do anything in the swig/ subdirectory.") - parser.add_argument("--skip-python", action="store_true", - help="Don't do anything in the python/ subdirectory.") - parser.add_argument("--skip-grc", action="store_true", - help="Don't do anything in the grc/ subdirectory.") - parser.add_argument("--scm-mode", choices=('yes', 'no', 'auto'), - default=gr.prefs().get_string('modtool', 'scm_mode', 'no'), - help="Use source control management [ yes | no | auto ]).") - parser.add_argument("-y", "--yes", action="store_true", - help="Answer all questions with 'yes'. This can overwrite and delete your files, so be careful.") - return parser - - def setup(self, options): - """ Initialise all internal variables, such as the module name etc. """ - self._dir = options.directory - if not self._check_directory(self._dir): - raise ModToolException('No GNU Radio module found in the given directory.') - if hasattr(options, 'module_name') and options.module_name is not None: - self._info['modname'] = options.module_name - else: - self._info['modname'] = get_modname() - if self._info['modname'] is None: - raise ModToolException('No GNU Radio module found in the given directory.') - 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'])) or - os.path.isdir(os.path.join('include', 'gnuradio', self._info['modname'])) - ): - self._info['version'] = '37' - if not os.path.isfile(os.path.join('cmake', 'Modules', 'FindCppUnit.cmake')): - self._info['version'] = '38' - 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.blockname - self._setup_files() - self._info['yes'] = options.yes - self.options = options - self._setup_scm() - - 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._info['pydir'] = 'python' - if os.path.isdir(os.path.join('python', self._info['modname'])): - self._info['pydir'] = os.path.join('python', self._info['modname']) - self._file['qalib'] = os.path.join('lib', 'qa_%s.cc' % self._info['modname']) - self._file['pyinit'] = os.path.join(self._info['pydir'], '__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(self._info['pydir'], 'CMakeLists.txt') - if self._info['is_component']: - self._info['includedir'] = os.path.join('include', 'gnuradio', self._info['modname']) - elif self._info['version'] in ('37', '38'): - 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') - self._file['cmfind'] = os.path.join('cmake', 'Modules', 'howtoConfig.cmake') - - - def _setup_scm(self, mode='active'): - """ Initialize source control management. """ - if mode == 'active': - self.scm = SCMRepoFactory(self.options, '.').make_active_scm_manager() - else: - self.scm = SCMRepoFactory(self.options, '.').make_empty_scm_manager() - if self.scm is None: - print("Error: Can't set up SCM.") - exit(1) - - 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 - self._info['is_component'] = False - for f in files: - if os.path.isfile(f) and f == 'CMakeLists.txt': - if re.search(r'find_package\(Gnuradio', 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 list(self._has_subdirs.keys())): - self._has_subdirs[f] = True - else: - self._skip_subdirs[f] = True - return bool(has_makefile and (list(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, options): - """ Override this. """ - raise NotImplementedError('Module implementation missing') - - -def get_modtool_modules(all_objects): - """Return list with all modtool modules.""" - modules = [] - for o in all_objects: - try: - if issubclass(o, ModTool) and o != ModTool: - modules.append(o) - except (TypeError, AttributeError): - pass - return modules - diff --git a/gr-utils/python/modtool/modtool_newmod.py b/gr-utils/python/modtool/modtool_newmod.py deleted file mode 100644 index c283204ee2..0000000000 --- a/gr-utils/python/modtool/modtool_newmod.py +++ /dev/null @@ -1,104 +0,0 @@ -# -# Copyright 2013 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. -# -""" Create a whole new out-of-tree module """ - -from __future__ import print_function -from __future__ import absolute_import -from __future__ import unicode_literals - -import shutil -import os -import re -from gnuradio import gr -from .modtool_base import ModTool, ModToolException -from .scm import SCMRepoFactory - -class ModToolNewModule(ModTool): - """ Create a new out-of-tree module """ - name = 'newmod' - description = 'Create new empty module, use add to add blocks.' - def __init__(self): - ModTool.__init__(self) - - @staticmethod - def setup_parser(parser): - " Initialise the option parser for 'gr_modtool newmod' " - parser.add_argument("--srcdir", - help="Source directory for the module template.") - parser.add_argument("module_name", metavar='MODULE-NAME', nargs='?', - help="Override the current module's name (normally is autodetected).") - - def setup(self, options): - # Don't call ModTool.setup(), that assumes an existing module. - self._info['modname'] = options.module_name - if self._info['modname'] is None: - if options.module_name: - self._info['modname'] = options.module_name - else: - self._info['modname'] = input('Name of the new module: ') - if not re.match('[a-zA-Z0-9_]+$', self._info['modname']): - raise ModToolException('Invalid module name.') - self._dir = options.directory - if self._dir == '.': - self._dir = './gr-%s' % self._info['modname'] - try: - os.stat(self._dir) - except OSError: - pass # This is what should happen - else: - raise ModToolException('The given directory exists.') - if options.srcdir is None: - options.srcdir = '/usr/local/share/gnuradio/modtool/gr-newmod' - self._srcdir = gr.prefs().get_string('modtool', 'newmod_path', options.srcdir) - if not os.path.isdir(self._srcdir): - raise ModToolException('Could not find gr-newmod source dir.') - self.options = options - self._setup_scm(mode='new') - - def run(self, options): - """ - * Copy the example dir recursively - * Open all files, rename howto and HOWTO to the module name - * Rename files and directories that contain the word howto - """ - self.setup(options) - print("Creating out-of-tree module in %s..." % (self._dir,)) - try: - shutil.copytree(self._srcdir, self._dir) - os.chdir(self._dir) - except OSError: - raise ModToolException('Could not create directory %s.' % self._dir) - 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.") - if self.scm.init_repo(path_to_repo="."): - print("Created repository... you might want to commit before continuing.") - print("Use 'gr_modtool add' to add a new block to this currently empty module.") - diff --git a/gr-utils/python/modtool/templates/CMakeLists.txt b/gr-utils/python/modtool/templates/CMakeLists.txt new file mode 100644 index 0000000000..9d69c5ce02 --- /dev/null +++ b/gr-utils/python/modtool/templates/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright 2018 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. + +include(GrPython) + +GR_PYTHON_INSTALL(FILES + __init__.py + templates.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/templates +) + +set(GR_PKG_MODTOOL_DATA_DIR ${GR_PKG_DATA_DIR}/modtool/templates) +install(DIRECTORY gr-newmod + DESTINATION ${GR_PKG_MODTOOL_DATA_DIR} +) diff --git a/gr-utils/python/modtool/templates/__init__.py b/gr-utils/python/modtool/templates/__init__.py new file mode 100644 index 0000000000..fcde85d69b --- /dev/null +++ b/gr-utils/python/modtool/templates/__init__.py @@ -0,0 +1,26 @@ +# +# Copyright 2018 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +from .templates import Templates diff --git a/gr-utils/python/modtool/gr-newmod/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/CMakeLists.txt index e03d787828..e03d787828 100644 --- a/gr-utils/python/modtool/gr-newmod/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/MANIFEST.md b/gr-utils/python/modtool/templates/gr-newmod/MANIFEST.md index cafdae8213..cafdae8213 100644 --- a/gr-utils/python/modtool/gr-newmod/MANIFEST.md +++ b/gr-utils/python/modtool/templates/gr-newmod/MANIFEST.md diff --git a/gr-utils/python/modtool/gr-newmod/apps/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/apps/CMakeLists.txt index e92732f65f..e92732f65f 100644 --- a/gr-utils/python/modtool/gr-newmod/apps/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/apps/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake index 66016cb2ff..66016cb2ff 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake diff --git a/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindCppUnit.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindCppUnit.cmake new file mode 100644 index 0000000000..f93ade3412 --- /dev/null +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindCppUnit.cmake @@ -0,0 +1,39 @@ +# http://www.cmake.org/pipermail/cmake/2006-October/011446.html +# Modified to use pkg config and use standard var names + +# +# Find the CppUnit includes and library +# +# This module defines +# CPPUNIT_INCLUDE_DIR, where to find tiff.h, etc. +# CPPUNIT_LIBRARIES, the libraries to link against to use CppUnit. +# CPPUNIT_FOUND, If false, do not try to use CppUnit. + +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_CPPUNIT "cppunit") + +FIND_PATH(CPPUNIT_INCLUDE_DIRS + NAMES cppunit/TestCase.h + HINTS ${PC_CPPUNIT_INCLUDE_DIR} + ${CMAKE_INSTALL_PREFIX}/include + PATHS + /usr/local/include + /usr/include +) + +FIND_LIBRARY(CPPUNIT_LIBRARIES + NAMES cppunit + HINTS ${PC_CPPUNIT_LIBDIR} + ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 + PATHS + ${CPPUNIT_INCLUDE_DIRS}/../lib + /usr/local/lib + /usr/lib +) + +LIST(APPEND CPPUNIT_LIBRARIES ${CMAKE_DL_LIBS}) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(CPPUNIT DEFAULT_MSG CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS) +MARK_AS_ADVANCED(CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS) diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake index 86044e52a3..86044e52a3 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrMiscUtils.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrMiscUtils.cmake index 6b7bbc9d20..6b7bbc9d20 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrMiscUtils.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrMiscUtils.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPlatform.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPlatform.cmake index ed9fc82580..ed9fc82580 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPlatform.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPlatform.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPython.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPython.cmake index 588236e8b8..588236e8b8 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPython.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPython.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrSwig.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrSwig.cmake index bd9fc8cb62..bd9fc8cb62 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrSwig.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrSwig.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrTest.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrTest.cmake index eee601a791..eee601a791 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrTest.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrTest.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/UseSWIG.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/UseSWIG.cmake index a37cc41ae4..a37cc41ae4 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/UseSWIG.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/UseSWIG.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/howtoConfig.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/howtoConfig.cmake index 88fbcad238..88fbcad238 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/howtoConfig.cmake +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/howtoConfig.cmake diff --git a/gr-utils/python/modtool/gr-newmod/cmake/cmake_uninstall.cmake.in b/gr-utils/python/modtool/templates/gr-newmod/cmake/cmake_uninstall.cmake.in index 9ae1ae4bd6..9ae1ae4bd6 100644 --- a/gr-utils/python/modtool/gr-newmod/cmake/cmake_uninstall.cmake.in +++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/cmake_uninstall.cmake.in diff --git a/gr-utils/python/modtool/gr-newmod/docs/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/docs/CMakeLists.txt index 8c86a96fc2..8c86a96fc2 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/docs/README.howto b/gr-utils/python/modtool/templates/gr-newmod/docs/README.howto index b29ce00579..b29ce00579 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/README.howto +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/README.howto diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/CMakeLists.txt index 7aa31fcecd..7aa31fcecd 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.in b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in index cabbb34167..cabbb34167 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.in +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in index 33bc2afd07..33bc2afd07 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/__init__.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/__init__.py index 22f4eb351d..22f4eb351d 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/__init__.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/__init__.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/base.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/base.py index b21369221c..b21369221c 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/base.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/base.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/doxyindex.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/doxyindex.py index 5401bf2666..5401bf2666 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/doxyindex.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/doxyindex.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py index 23095c1f34..23095c1f34 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compound.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compound.py index acfa0dd5c6..acfa0dd5c6 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compound.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compound.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py index 6e984e13ec..6e984e13ec 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/index.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/index.py index 0c63512119..0c63512119 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/index.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/index.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py index 11312db635..11312db635 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/text.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/text.py index caf0b05480..caf0b05480 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/text.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/text.py diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/group_defs.dox b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/group_defs.dox index 708f8c6d98..708f8c6d98 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/group_defs.dox +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/group_defs.dox diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/main_page.dox b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/main_page.dox index 6357044912..6357044912 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/main_page.dox +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/main_page.dox diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/swig_doc.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py index 07c5b75401..07c5b75401 100644 --- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/swig_doc.py +++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py diff --git a/gr-utils/python/modtool/gr-newmod/examples/README b/gr-utils/python/modtool/templates/gr-newmod/examples/README index c012bdfa0a..c012bdfa0a 100644 --- a/gr-utils/python/modtool/gr-newmod/examples/README +++ b/gr-utils/python/modtool/templates/gr-newmod/examples/README diff --git a/gr-utils/python/modtool/gr-newmod/grc/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/grc/CMakeLists.txt index b0ce5e79a9..b0ce5e79a9 100644 --- a/gr-utils/python/modtool/gr-newmod/grc/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/grc/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/include/howto/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/include/howto/CMakeLists.txt index 664ca26a34..664ca26a34 100644 --- a/gr-utils/python/modtool/gr-newmod/include/howto/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/include/howto/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/include/howto/api.h b/gr-utils/python/modtool/templates/gr-newmod/include/howto/api.h index 03620be2df..03620be2df 100644 --- a/gr-utils/python/modtool/gr-newmod/include/howto/api.h +++ b/gr-utils/python/modtool/templates/gr-newmod/include/howto/api.h diff --git a/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/lib/CMakeLists.txt index 2460b930f6..2460b930f6 100644 --- a/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/lib/CMakeLists.txt diff --git a/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.cc b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.cc new file mode 100644 index 0000000000..2f10a3a564 --- /dev/null +++ b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.cc @@ -0,0 +1,37 @@ +/* + * Copyright 2012 Free Software Foundation, Inc. + * + * This file was generated by gr_modtool, a tool from the GNU Radio framework + * This file is a part of gr-howto + * + * 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. + */ + +/* + * This class gathers together all the test cases for the gr-filter + * directory into a single test suite. As you create new test cases, + * add them here. + */ + +#include "qa_howto.h" + +CppUnit::TestSuite * +qa_howto::suite() +{ + CppUnit::TestSuite *s = new CppUnit::TestSuite("howto"); + + return s; +} diff --git a/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.h b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.h new file mode 100644 index 0000000000..c50bde3ec7 --- /dev/null +++ b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.h @@ -0,0 +1,39 @@ +/* -*- c++ -*- */ +/* + * Copyright 2012 Free Software Foundation, Inc. + * + * This file was generated by gr_modtool, a tool from the GNU Radio framework + * This file is a part of gr-howto + * + * 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. + */ + +#ifndef _QA_HOWTO_H_ +#define _QA_HOWTO_H_ + +#include <gnuradio/attributes.h> +#include <cppunit/TestSuite.h> + +//! collect all the tests for the gr-filter directory + +class __GR_ATTR_EXPORT qa_howto +{ + public: + //! return suite of tests for all of gr-filter directory + static CppUnit::TestSuite *suite(); +}; + +#endif /* _QA_HOWTO_H_ */ diff --git a/gr-utils/python/modtool/templates/gr-newmod/lib/test_howto.cc b/gr-utils/python/modtool/templates/gr-newmod/lib/test_howto.cc new file mode 100644 index 0000000000..699bb5b640 --- /dev/null +++ b/gr-utils/python/modtool/templates/gr-newmod/lib/test_howto.cc @@ -0,0 +1,49 @@ +/* -*- c++ -*- */ +/* + * Copyright 2012 Free Software Foundation, Inc. + * + * This file was generated by gr_modtool, a tool from the GNU Radio framework + * This file is a part of gr-howto + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <cppunit/TextTestRunner.h> +#include <cppunit/XmlOutputter.h> + +#include <gnuradio/unittests.h> +#include "qa_howto.h" +#include <iostream> +#include <fstream> + +int +main (int argc, char **argv) +{ + CppUnit::TextTestRunner runner; + std::ofstream xmlfile(get_unittest_path("howto.xml").c_str()); + CppUnit::XmlOutputter *xmlout = new CppUnit::XmlOutputter(&runner.result(), xmlfile); + + runner.addTest(qa_howto::suite()); + runner.setOutputter(xmlout); + + bool was_successful = runner.run("", false); + + return was_successful ? 0 : 1; +} diff --git a/gr-utils/python/modtool/gr-newmod/python/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/python/CMakeLists.txt index 4b5303cc6f..4b5303cc6f 100644 --- a/gr-utils/python/modtool/gr-newmod/python/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/python/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/python/__init__.py b/gr-utils/python/modtool/templates/gr-newmod/python/__init__.py index 806f287cc3..06b4f6057a 100644 --- a/gr-utils/python/modtool/gr-newmod/python/__init__.py +++ b/gr-utils/python/modtool/templates/gr-newmod/python/__init__.py @@ -26,10 +26,10 @@ from __future__ import unicode_literals # import swig generated symbols into the howto namespace try: - # this might fail if the module is python-only - from howto_swig import * + # this might fail if the module is python-only + from .howto_swig import * except ImportError: - pass + pass # import any pure python here # diff --git a/gr-utils/python/modtool/gr-newmod/swig/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/swig/CMakeLists.txt index bc11347447..bc11347447 100644 --- a/gr-utils/python/modtool/gr-newmod/swig/CMakeLists.txt +++ b/gr-utils/python/modtool/templates/gr-newmod/swig/CMakeLists.txt diff --git a/gr-utils/python/modtool/gr-newmod/swig/howto_swig.i b/gr-utils/python/modtool/templates/gr-newmod/swig/howto_swig.i index 4627d48d34..ea05727d94 100644 --- a/gr-utils/python/modtool/gr-newmod/swig/howto_swig.i +++ b/gr-utils/python/modtool/templates/gr-newmod/swig/howto_swig.i @@ -2,7 +2,7 @@ #define HOWTO_API -%include "gnuradio.i" // the common stuff +%include "gnuradio.i" // the common stuff //load generated python docstrings %include "howto_swig_doc.i" diff --git a/gr-utils/python/modtool/templates.py b/gr-utils/python/modtool/templates/templates.py index 1ba7732d7c..f051c3d0f9 100644 --- a/gr-utils/python/modtool/templates.py +++ b/gr-utils/python/modtool/templates/templates.py @@ -1,5 +1,5 @@ # -# Copyright 2013-2014,2018 Free Software Foundation, Inc. +# Copyright 2013-2014, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -19,6 +19,9 @@ # Boston, MA 02110-1301, USA. # ''' All the templates for skeleton files (needed by ModToolAdd) ''' + +from __future__ import print_function +from __future__ import absolute_import from __future__ import unicode_literals from datetime import datetime @@ -396,16 +399,16 @@ class ${blockname}(${parenttype}): ${parenttype}.__init__(self, % if blocktype == 'hier': "${blockname}", - gr.io_signature(${inputsig}), # Input signature - gr.io_signature(${outputsig})) # Output signature + "gr.io_signature(${inputsig})", # Input signature + "gr.io_signature(${outputsig})") # Output signature # Define blocks and connect them self.connect() <% return %> % else: name="${blockname}", - in_sig=${inputsig}, - out_sig=${outputsig}${deciminterp}) + in_sig="${inputsig}", + out_sig="${outputsig}${deciminterp}") % endif % if blocktype == 'general': @@ -416,8 +419,8 @@ class ${blockname}(${parenttype}): 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])) + consume(0, len(input_items[0]))\ + #self.consume_each(len(input_items[0])) return len(output_items[0]) <% return %> % endif @@ -524,13 +527,13 @@ ${str_to_python_comment(license)} from gnuradio import gr, gr_unittest from gnuradio import blocks % if lang == 'cpp': -import ${modname}.${modname}_swig as ${modname} +from . import ${modname}_swig as ${modname} % else: -from ${blockname} import ${blockname} +from .${blockname} import ${blockname} % endif - class qa_${blockname}(gr_unittest.TestCase): + def setUp(self): self.tb = gr.top_block() @@ -544,47 +547,52 @@ class qa_${blockname}(gr_unittest.TestCase): if __name__ == '__main__': - gr_unittest.run(qa_${blockname}, "qa_${blockname}.xml") + gr_unittest.run(qa_${blockname}, "qa_${blockname}.yml") ''' -Templates['grc_xml'] = r'''<?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_grc(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, float, 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, float, complex, byte, short, xxx_vector, ...--></type> - </source> -</block> +Templates['grc_yml'] = '''id: ${modname}_${blockname} +label: ${blockname} +category: [${modname}] + +templates: + imports: import ${modname} + make: ${modname}.${blockname}(${strip_arg_types_grc(arglist)}) + +<!-- Make one 'parameters' list entry for every Parameter you want settable from the GUI. + Sub-entries of dictionary: + * id (makes the value accessible as \$keyname, e.g. in the make entry) + * label + * dtype --> +parameters: +- id: ... + label: ... + dtype: ... +- id: ... + label: ... + dtype: ... + +<!-- Make one 'inputs' list entry per input. Sub-entries of dictionary: + * label (an identifier for the GUI) + * domain + * dtype + * vlen + * optional (set to 1 for optional inputs) --> +inputs: +- label: ... + domain: ... + dtype: ... + vlen: ... + +<!-- Make one 'outputs' list entry per output. Sub-entries of dictionary: + * label (an identifier for the GUI) + * dtype + * vlen + * optional (set to 1 for optional inputs) --> +- label: ... + domain: ... + dtype: !-- e.g. int, float, complex, byte, short, xxx_vector, ...-- + +file_format: 1 ''' # SWIG string diff --git a/gr-utils/python/modtool/tests/CMakeLists.txt b/gr-utils/python/modtool/tests/CMakeLists.txt new file mode 100644 index 0000000000..beb77cba15 --- /dev/null +++ b/gr-utils/python/modtool/tests/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright 2018 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. + +include(GrPython) + +GR_PYTHON_INSTALL(FILES + __init__.py + test_modtool.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/tests +) + diff --git a/gr-utils/python/modtool/tests/__init__.py b/gr-utils/python/modtool/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/gr-utils/python/modtool/tests/__init__.py diff --git a/gr-utils/python/modtool/tests/test_modtool.py b/gr-utils/python/modtool/tests/test_modtool.py new file mode 100644 index 0000000000..111c88a7af --- /dev/null +++ b/gr-utils/python/modtool/tests/test_modtool.py @@ -0,0 +1,294 @@ +# +# Copyright 2018 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. +# +""" The file for testing the gr-modtool scripts """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +import shutil +import tempfile +import unittest +import warnings +from os import path +from pylint.epylint import py_run + +from gnuradio.modtool.core import * + +class TestModToolCore(unittest.TestCase): + """ The tests for the modtool core """ + def __init__(self, *args, **kwargs): + super(TestModToolCore, self).__init__(*args, **kwargs) + self.f_add = False + self.f_newmod = False + + @classmethod + def setUpClass(cls): + """ create a temporary directory """ + cls.test_dir = tempfile.mkdtemp() + + @classmethod + def tearDownClass(cls): + """ remove the directory after the test """ + shutil.rmtree(cls.test_dir) + + def setUp(self): + """ create a new module and block before every test """ + try: + warnings.simplefilter("ignore", ResourceWarning) + args = {'module_name':'howto', 'directory': self.test_dir} + ModToolNewModule(**args).run() + except (TypeError, ModToolException): + self.f_newmod = True + else: + try: + args = {'blockname':'square_ff', 'block_type':'general', + 'lang':'cpp', 'directory': self.test_dir + '/gr-howto', + 'add_python_qa': True} + ModToolAdd(**args).run() + except (TypeError, ModToolException): + self.f_add = True + + def tearDown(self): + """ removes the created module """ + # Required, else the new-module directory command + # in setup will throw exception after first test + ## cannot remove if directory is not created + if not self.f_newmod: + rmdir = self.test_dir + '/gr-howto' + shutil.rmtree(rmdir) + + def test_newmod(self): + """ Tests for the API function newmod """ + ## Tests for proper exceptions ## + test_dict = {} + test_dict['directory'] = self.test_dir + # module name not specified + self.assertRaises(ModToolException, ModToolNewModule(**test_dict).run) + test_dict['module_name'] = 'howto' + # expected module_name as a string instead of dict + self.assertRaises(TypeError, ModToolNewModule(test_dict).run) + # directory already exists + # will not be raised if the command in setup failed + self.assertRaises(ModToolException, ModToolNewModule(**test_dict).run) + + ## Some tests for checking the created directory, sub-directories and files ## + test_dict['module_name'] = 'test' + ModToolNewModule(**test_dict).run() + module_dir = path.join(self.test_dir, 'gr-test') + self.assertTrue(path.isdir(module_dir)) + self.assertTrue(path.isdir(path.join(module_dir, 'lib'))) + self.assertTrue(path.isdir(path.join(module_dir, 'python'))) + self.assertTrue(path.isdir(path.join(module_dir, 'include'))) + self.assertTrue(path.isdir(path.join(module_dir, 'docs'))) + self.assertTrue(path.isdir(path.join(module_dir, 'cmake'))) + self.assertTrue(path.isdir(path.join(module_dir, 'swig'))) + self.assertTrue(path.exists(path.join(module_dir, 'CMakeLists.txt'))) + + ## pylint tests ## + python_dir = path.join(module_dir, 'python') + py_module = path.join(python_dir, 'build_utils.py') + (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True) + print(pylint_stdout.getvalue(), end='') + py_module = path.join(python_dir, 'build_utils_codes.py') + (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True) + print(pylint_stdout.getvalue(), end='') + + ## The check for object instantiation ## + test_obj = ModToolNewModule() + # module name not specified + self.assertRaises(ModToolException, test_obj.run) + test_obj.info['modname'] = 'howto' + test_obj.directory = self.test_dir + # directory already exists + self.assertRaises(ModToolException, test_obj.run) + test_obj.info['modname'] = 'test1' + test_obj.run() + self.assertTrue(path.isdir(self.test_dir+'/gr-test1')) + self.assertTrue(path.isdir(self.test_dir+'/gr-test1/lib')) + self.assertTrue(path.exists(self.test_dir+'/gr-test1/CMakeLists.txt')) + + def test_add(self): + """ Tests for the API function add """ + ## skip tests if newmod command wasn't successful + if self.f_newmod: + raise unittest.SkipTest("setUp for API function 'add' failed") + module_dir = path.join(self.test_dir, 'gr-howto') + ## Tests for proper exceptions ## + test_dict = {} + test_dict['directory'] = module_dir + # missing blockname, block_type, lang + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + test_dict['blockname'] = 'add_ff' + # missing arguments block_type, lang + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + test_dict['block_type'] = 'general' + # missing argument lang + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + test_dict['lang'] = 'cxx' + # incorrect language + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + test_dict['lang'] = 'cpp' + test_dict['add_cpp_qa'] = 'Wrong' + # boolean is expected for add_cpp_qa + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + test_dict['add_cpp_qa'] = True + test_dict['block_type'] = 'generaleee' + # incorrect block type + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + test_dict['block_type'] = 'general' + test_dict['skip_lib'] = 'fail' + # boolean value is expected for skip_lib, fails in instantiation + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + test_dict['skip_lib'] = True + # missing relevant subdir + self.assertRaises(ModToolException, ModToolAdd(**test_dict).run) + + ## Some tests for checking the created directory, sub-directories and files ## + test_dict['skip_lib'] = False + ModToolAdd(**test_dict).run() + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'qa_add_ff.cc'))) + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'add_ff_impl.cc'))) + self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_add_ff.block.yml'))) + self.assertTrue(path.exists(path.join(module_dir, 'include', 'howto', 'add_ff.h'))) + + ## The check for object instantiation ## + test_obj = ModToolAdd() + test_obj.dir = module_dir + # missing blocktype, lang, blockname + self.assertRaises(ModToolException, test_obj.run) + test_obj.info['blocktype'] = 'general' + # missing lang, blockname + self.assertRaises(ModToolException, test_obj.run) + test_obj.info['lang'] = 'python' + test_obj.info['blockname'] = 'mul_ff' + test_obj.add_py_qa = True + test_obj.run() + self.assertTrue(path.exists(path.join(module_dir, 'python', 'mul_ff.py'))) + self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_mul_ff.py'))) + self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_mul_ff.block.yml'))) + + ## pylint tests ## + python_dir = path.join(module_dir, 'python') + py_module = path.join(python_dir, 'mul_ff.py') + (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only --disable=E0602', return_std=True) + print(pylint_stdout.getvalue(), end='') + py_module = path.join(python_dir, 'qa_mul_ff.py') + (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True) + print(pylint_stdout.getvalue(), end='') + + def test_rename(self): + """ Tests for the API function rename """ + if self.f_newmod or self.f_add: + raise unittest.SkipTest("setUp for API function 'rename' failed") + + module_dir = path.join(self.test_dir, 'gr-howto') + test_dict = {} + test_dict['directory'] = module_dir + # Missing 2 arguments blockname, new_name + self.assertRaises(ModToolException, ModToolRename(**test_dict).run) + test_dict['blockname'] = 'square_ff' + # Missing argument new_name + self.assertRaises(ModToolException, ModToolRename(**test_dict).run) + test_dict['new_name'] = '//#' + # Invalid new block name! + self.assertRaises(ModToolException, ModToolRename(**test_dict).run) + test_dict['new_name'] = None + # New Block name not specified + self.assertRaises(ModToolException, ModToolRename(**test_dict).run) + + ## Some tests for checking the renamed files ## + test_dict['new_name'] = 'div_ff' + ModToolRename(**test_dict).run() + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'div_ff_impl.h'))) + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'div_ff_impl.cc'))) + self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_div_ff.py'))) + self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_div_ff.block.yml'))) + + ## The check for object instantiation ## + test_obj = ModToolRename() + test_obj.info['oldname'] = 'div_ff' + test_obj.info['newname'] = 'sub_ff' + test_obj.run() + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'sub_ff_impl.h'))) + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'sub_ff_impl.cc'))) + self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_sub_ff.py'))) + self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_sub_ff.block.yml'))) + + def test_remove(self): + """ Tests for the API function remove """ + if self.f_newmod or self.f_add: + raise unittest.SkipTest("setUp for API function 'remove' failed") + module_dir = path.join(self.test_dir, 'gr-howto') + test_dict = {} + # missing argument blockname + self.assertRaises(ModToolException, ModToolRename(**test_dict).run) + test_dict['directory'] = module_dir + self.assertRaises(ModToolException, ModToolRename(**test_dict).run) + + ## Some tests to check blocks are not removed with different blocknames ## + test_dict['blockname'] = 'div_ff' + ModToolRemove(**test_dict).run() + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'square_ff_impl.h'))) + self.assertTrue(path.exists(path.join(module_dir, 'lib', 'square_ff_impl.cc'))) + self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_square_ff.py'))) + self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_square_ff.block.yml'))) + + ## Some tests for checking the non-existence of removed files ## + test_dict['blockname'] = 'square_ff' + ModToolRemove(**test_dict).run() + self.assertTrue(not path.exists(path.join(module_dir, 'lib', 'square_ff_impl.h'))) + self.assertTrue(not path.exists(path.join(module_dir, 'lib', 'square_ff_impl.cc'))) + self.assertTrue(not path.exists(path.join(module_dir, 'python', 'qa_square_ff.py'))) + self.assertTrue(not path.exists(path.join(module_dir, 'grc', 'howto_square_ff.block.yml'))) + + def test_makeyaml(self): + """ Tests for the API function makeyaml """ + if self.f_newmod or self.f_add: + raise unittest.SkipTest("setUp for API function 'makeyaml' failed") + module_dir = path.join(self.test_dir, 'gr-howto') + test_dict = {} + # missing argument blockname + self.assertRaises(ModToolException, ModToolMakeYAML(**test_dict).run) + test_dict['directory'] = module_dir + self.assertRaises(ModToolException, ModToolMakeYAML(**test_dict).run) + + ## Some tests to check if the command reuns ## + test_dict['blockname'] = 'square_ff' + ModToolMakeYAML(**test_dict).run() + + def test_disable(self): + """ Tests for the API function disable """ + if self.f_newmod or self.f_add: + raise unittest.SkipTest("setUp for API function 'disable' failed") + module_dir = path.join(self.test_dir, 'gr-howto') + test_dict = {} + # missing argument blockname + self.assertRaises(ModToolException, ModToolDisable(**test_dict).run) + test_dict['directory'] = module_dir + self.assertRaises(ModToolException, ModToolDisable(**test_dict).run) + + ## Some tests to check if the command reuns ## + test_dict['blockname'] = 'square_ff' + ModToolDisable(**test_dict).run() + +if __name__ == '__main__': + unittest.main() diff --git a/gr-utils/python/modtool/tools/CMakeLists.txt b/gr-utils/python/modtool/tools/CMakeLists.txt new file mode 100644 index 0000000000..f9acf9bfe7 --- /dev/null +++ b/gr-utils/python/modtool/tools/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright 2018 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. + +include(GrPython) + +GR_PYTHON_INSTALL(FILES + __init__.py + cmakefile_editor.py + code_generator.py + grc_yaml_generator.py + parser_cc_block.py + scm.py + util_functions.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/tools +) diff --git a/gr-utils/python/modtool/tools/__init__.py b/gr-utils/python/modtool/tools/__init__.py new file mode 100644 index 0000000000..11cff70d59 --- /dev/null +++ b/gr-utils/python/modtool/tools/__init__.py @@ -0,0 +1,31 @@ +# +# Copyright 2018 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +from .cmakefile_editor import CMakeFileEditor +from .code_generator import render_template +from .grc_yaml_generator import GRCYAMLGenerator +from .parser_cc_block import ParserCCBlock +from .scm import SCMRepoFactory +from .util_functions import *
\ No newline at end of file diff --git a/gr-utils/python/modtool/cmakefile_editor.py b/gr-utils/python/modtool/tools/cmakefile_editor.py index 40d88fac18..0e3ede0be9 100644 --- a/gr-utils/python/modtool/cmakefile_editor.py +++ b/gr-utils/python/modtool/tools/cmakefile_editor.py @@ -1,5 +1,5 @@ # -# Copyright 2013 Free Software Foundation, Inc. +# Copyright 2013, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -21,21 +21,27 @@ """ Edit CMakeLists.txt files """ from __future__ import print_function +from __future__ import absolute_import from __future__ import unicode_literals import re +import logging + +logger = logging.getLogger(__name__) + class CMakeFileEditor(object): """A tool for editing CMakeLists.txt files. """ def __init__(self, filename, separator='\n ', indent=' '): self.filename = filename - self.cfile = open(filename, 'r').read() + with open(filename, 'r') as f: + self.cfile = f.read() self.separator = separator self.indent = indent def append_value(self, entry, value, to_ignore_start='', to_ignore_end=''): """ Add a value to an entry. """ - regexp = re.compile(r'(%s\(%s[^()]*?)\s*?(\s?%s)\)' % (entry, to_ignore_start, to_ignore_end), + regexp = re.compile(r'({}\({}[^()]*?)\s*?(\s?{})\)'.format(entry, to_ignore_start, to_ignore_end), re.MULTILINE) substi = r'\1' + self.separator + value + r'\2)' (self.cfile, nsubs) = regexp.subn(substi, self.cfile, count=1) @@ -80,14 +86,15 @@ class CMakeFileEditor(object): def delete_entry(self, entry, value_pattern=''): """Remove an entry from the current buffer.""" - regexp = r'%s\s*\([^()]*%s[^()]*\)[^\n]*\n' % (entry, value_pattern) + regexp = r'{}\s*\([^()]*{}[^()]*\)[^\n]*\n'.format(entry, value_pattern) regexp = re.compile(regexp, re.MULTILINE) (self.cfile, nsubs) = re.subn(regexp, '', self.cfile, count=1) return nsubs def write(self): """ Write the changes back to the file. """ - open(self.filename, 'w').write(self.cfile) + with open(self.filename, 'w') as f: + f.write(self.cfile) def remove_double_newlines(self): """Simply clear double newlines from the file buffer.""" @@ -129,9 +136,9 @@ class CMakeFileEditor(object): 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) + logger.warning("Warning: A replacement failed when commenting out {}. Check the CMakeFile.txt manually.".format(fname)) elif nsubs > 1: - print("Warning: Replaced %s %d times (instead of once). Check the CMakeFile.txt manually." % (fname, nsubs)) + logger.warning("Warning: Replaced {} {} times (instead of once). Check the CMakeFile.txt manually.".format(fname, nsubs)) def comment_out_lines(self, pattern, comment_str='#'): """ Comments out all lines that match with pattern """ @@ -141,5 +148,5 @@ class CMakeFileEditor(object): 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('*', r'\*') + glob_re = r'GLOB\s[a-z_]+\s"{}"'.format(globstr.replace('*', r'\*')) return re.search(glob_re, self.cfile, flags=re.MULTILINE|re.IGNORECASE) is not None diff --git a/gr-utils/python/modtool/code_generator.py b/gr-utils/python/modtool/tools/code_generator.py index d95434f577..eccecf895b 100644 --- a/gr-utils/python/modtool/code_generator.py +++ b/gr-utils/python/modtool/tools/code_generator.py @@ -19,11 +19,13 @@ # Boston, MA 02110-1301, USA. # """ A code generator (needed by ModToolAdd) """ + +from __future__ import print_function from __future__ import absolute_import from __future__ import unicode_literals from mako.template import Template -from .templates import Templates +from ..templates import Templates from .util_functions import str_to_fancyc_comment from .util_functions import str_to_python_comment from .util_functions import strip_default_values diff --git a/gr-utils/python/modtool/tools/grc_yaml_generator.py b/gr-utils/python/modtool/tools/grc_yaml_generator.py new file mode 100644 index 0000000000..ce61f46069 --- /dev/null +++ b/gr-utils/python/modtool/tools/grc_yaml_generator.py @@ -0,0 +1,138 @@ +# +# Copyright 2018 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 generating YAML bindings """ + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import unicode_literals + +from collections import OrderedDict + +import yaml +from yaml import CLoader as Loader, CDumper as Dumper + +from .util_functions import is_number + + +## setup dumper for dumping OrderedDict ## +_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG + +def dict_representer(dumper, data): + """ Representer to represent special OrderedDict """ + return dumper.represent_dict(data.items()) + + +def dict_constructor(loader, node): + """ Construct an OrderedDict for dumping """ + return OrderedDict(loader.construct_pairs(node)) + +Dumper.add_representer(OrderedDict, dict_representer) +Loader.add_constructor(_mapping_tag, dict_constructor) + + +class GRCYAMLGenerator(object): + """ Create and write the YAML 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 = (('id', '{}_{}'.format(modname, blockname)), + ('label', blockname.replace('_', ' ').capitalize()), + ('category', '[{}]'.format(modname.upper())) + ) + self._templates = (('imports', 'import {}'.format(modname)), + ('make', '{}.{}({})'.format(modname, blockname, ', '.join(params_list))) + ) + self.params = params + self.iosig = iosig + self.doc = doc + self.data = None + + def make_yaml(self): + """ Create the actual tag tree """ + data = OrderedDict() + for tag, value in self._header: + data[tag] = value + + templates = OrderedDict() + for tag, value in self._templates: + templates[tag] = value + + data['templates'] = templates + + parameters = [] + for param in self.params: + parameter = OrderedDict() + parameter['id'] = param['key'] + parameter['label'] = param['key'].capitalize() + if param['default']: + parameter['default'] = param['default'] + parameter['dtype'] = param['type'] + parameters.append(parameter) + + if parameters: + data['parameters'] = parameters + + inputs = [] + outputs = [] + iosig = self.iosig + for inout in sorted(iosig.keys()): + if iosig[inout]['max_ports'] == '0': + continue + for i in range(len(iosig[inout]['type'])): + s_type = {'in': 'input', 'out': 'output'}[inout] + s_obj = OrderedDict() + s_obj['label'] = inout + s_obj['domain'] = 'stream' + s_obj['dtype'] = iosig[inout]['type'][i] + if iosig[inout]['vlen'][i] != '1': + vlen = iosig[inout]['vlen'][i] + if is_number(vlen): + s_obj['vlen'] = vlen + else: + s_obj['vlen'] = '${ '+vlen+' }' + if i == len(iosig[inout]['type'])-1: + if not is_number(iosig[inout]['max_ports']): + s_obj['multiplicity'] = iosig[inout]['max_ports'] + elif len(iosig[inout]['type']) < int(iosig[inout]['max_ports']): + s_obj['multiplicity'] = str(int(iosig[inout]['max_ports']) - + len(iosig[inout]['type'])+1) + if s_type == 'input': + inputs.append(s_obj) + elif s_type == 'output': + outputs.append(s_obj) + + if inputs: + data['inputs'] = inputs + + if outputs: + data['outputs'] = outputs + + if self.doc is not None: + data['documentation'] = self.doc + self.data = data + data['file_format'] = 1 + + def save(self, filename): + """ Write the YAML file """ + self.make_yaml() + with open(filename, 'w') as f: + yaml.dump(self.data, f, Dumper=Dumper, default_flow_style=False) diff --git a/gr-utils/python/modtool/parser_cc_block.py b/gr-utils/python/modtool/tools/parser_cc_block.py index 766b063ccd..5ed185d87f 100644 --- a/gr-utils/python/modtool/parser_cc_block.py +++ b/gr-utils/python/modtool/tools/parser_cc_block.py @@ -1,5 +1,5 @@ # -# Copyright 2013 Free Software Foundation, Inc. +# Copyright 2013, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -21,10 +21,14 @@ ''' A parser for blocks written in C++ ''' from __future__ import print_function +from __future__ import absolute_import from __future__ import unicode_literals import re import sys +import logging + +logger = logging.getLogger(__name__) def dummy_translator(the_type, default_v=None): """ Doesn't really translate. """ @@ -33,8 +37,10 @@ def dummy_translator(the_type, default_v=None): 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() + with open(filename_cc) as f: + self.code_cc = f.read() + with open(filename_h) as f: + self.code_h = f.read() self.blockname = blockname self.type_trans = type_trans self.version = version @@ -46,7 +52,7 @@ class ParserCCBlock(object): E.g., for sizeof(int), it will return 'int'. Returns a list! """ if 'gr::io_signature::makev' in iosigcall: - print('tbi') + logger.error('tbi') raise ValueError return {'type': [_typestr_to_iotype(x) for x in typestr.split(',')], 'vlen': [_typestr_to_vlen(x) for x in typestr.split(',')] @@ -87,14 +93,14 @@ class ParserCCBlock(object): iosig['in']['min_ports'] = iosig_match.group('inmin') iosig['in']['max_ports'] = iosig_match.group('inmax') except Exception: - print("Error: Can't parse input signature.") + logger.error("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 Exception: - print("Error: Can't parse output signature.") + logger.error("Error: Can't parse output signature.") return iosig @@ -127,8 +133,9 @@ class ParserCCBlock(object): if parens_count == 0: if read_state == 'type' and len(this_type): raise ValueError( - 'Found closing parentheses before finishing last argument (this is how far I got: %s)' - % str(param_list) + 'Found closing parentheses before finishing ' + 'last argument (this is how far I got: {})'.format \ + (str(param_list)) ) if len(this_type): param_list.append((this_type, this_name, this_defv)) @@ -169,16 +176,16 @@ class ParserCCBlock(object): 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) + 'While parsing argument {} ({}): name finished but no closing parentheses.'.format \ + (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) + 'While parsing argument {} ({}): name finished but no closing parentheses.'.format \ + (len(param_list)+1, this_type + ' ' + this_name) ) read_state = 'defv' else: @@ -195,8 +202,8 @@ class ParserCCBlock(object): 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) + 'While parsing argument {} ({}): default value finished but no closing parentheses.'.format \ + (len(param_list)+1, this_type + ' ' + this_name) ) read_state = 'type' param_list.append((this_type, this_name, this_defv)) @@ -217,7 +224,7 @@ class ParserCCBlock(object): try: params_list = _scan_param_list(make_match.end(0)) except ValueError as ve: - print("Can't parse the argument list: ", ve.args[0]) + logger.error("Can't parse the argument list: ", ve.args[0]) sys.exit(0) params = [] for plist in params_list: diff --git a/gr-utils/python/modtool/scm.py b/gr-utils/python/modtool/tools/scm.py index 4b3d5116ac..249616ade3 100644 --- a/gr-utils/python/modtool/scm.py +++ b/gr-utils/python/modtool/tools/scm.py @@ -21,10 +21,14 @@ """ Class to handle source code management repositories. """ from __future__ import print_function +from __future__ import absolute_import from __future__ import unicode_literals +import logging import subprocess +logger = logging.getLogger(__name__) + try: import git HAS_GITPYTHON = True @@ -206,7 +210,7 @@ class SCMRepoFactory(object): if issubclass(glbl, SCMRepository): the_scm = glbl(self.path_to_repo) if the_scm.is_active(): - print('Found SCM of type:', the_scm.handles_scm_type) + logger.info('Found SCM of type:', the_scm.handles_scm_type) return the_scm except (TypeError, AttributeError, InvalidSCMError): pass diff --git a/gr-utils/python/modtool/util_functions.py b/gr-utils/python/modtool/tools/util_functions.py index edd98fde8f..15409eacc3 100644 --- a/gr-utils/python/modtool/util_functions.py +++ b/gr-utils/python/modtool/tools/util_functions.py @@ -20,6 +20,8 @@ # """ Utility functions for gr_modtool """ +from __future__ import print_function +from __future__ import absolute_import from __future__ import unicode_literals import re @@ -32,20 +34,25 @@ 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() + with open(filename, 'r') as f: + oldfile = f.read() lines = re.findall(linepattern, oldfile, flags=re.MULTILINE) if len(lines) == 0: - open(filename, 'a').write(newline) + with open(filename, 'a') as f: + f.write(newline) return last_line = lines[-1] newfile = oldfile.replace(last_line, last_line + newline + '\n') - open(filename, 'w').write(newfile) + with open(filename, 'w') as f: + f.write(newfile) def remove_pattern_from_file(filename, pattern): """ Remove all occurrences of a given pattern from a file. """ - oldfile = open(filename, 'r').read() + with open(filename, 'r') as f: + oldfile = f.read() pattern = re.compile(pattern, re.MULTILINE) - open(filename, 'w').write(pattern.sub('', oldfile)) + with open(filename, 'w') as f: + f.write(pattern.sub('', oldfile)) def str_to_fancyc_comment(text): """ Return a string as a C formatted comment. """ @@ -90,13 +97,15 @@ 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() + with open('gnuradio.project', 'r') as f: + prfile = f.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() + with open('CMakeLists.txt', 'r') as f: + cmfile = f.read() regexp = r'(project\s*\(\s*|GR_REGISTER_COMPONENT\(")gr-(?P<modname>[a-zA-Z0-9-_]+)(\s*(CXX)?|" ENABLE)' try: modname = re.search(regexp, cmfile, flags=re.MULTILINE).group('modname').strip() @@ -114,22 +123,6 @@ def is_number(s): 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. """ @@ -156,7 +149,7 @@ class SequenceCompleter(object): if not text and state < len(self._seq): return self._seq[state] if not state: - self._tmp_matches = filter(lambda candidate: candidate.startswith(text), self._seq) + self._tmp_matches = [candidate for candidate in self._seq if candidate.startswith(text)] if state < len(self._tmp_matches): return self._tmp_matches[state] diff --git a/gr-utils/python/utils/gr_modtool b/gr-utils/python/utils/gr_modtool index f959c91f66..4f2738901e 100755 --- a/gr-utils/python/utils/gr_modtool +++ b/gr-utils/python/utils/gr_modtool @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2012 Free Software Foundation, Inc. +# Copyright 2012, 2018 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -21,39 +21,12 @@ # """ A tool for editing GNU Radio out-of-tree modules. """ -from __future__ import print_function -from gnuradio.modtool import * +from gnuradio.modtool.cli.base import cli -def setup_parser(): - modules = get_modtool_modules(ModTool.__subclasses__()) - parser = ModTool.get_parser() - subparsers = parser.add_subparsers(title="Commands") - epilog = [] - for module in modules: - subparser = subparsers.add_parser(module.name, - description=module.description) - module.setup_parser(subparser) - subparser.set_defaults(module=module) - epilog.append(" {:<22}{}".format(module.name, module.description)) - parser.epilog = '\n'.join(epilog) - return parser - -def main(): - """ Here we go. Parse command, choose class and run. """ - parser = setup_parser() - args = parser.parse_args() - - try: - args.module().run(args) - except ModToolException as err: - print(err, file=sys.stderr) - exit(1) - if __name__ == '__main__': try: - main() + cli() except KeyboardInterrupt: pass - |