From 0d727b3ef8ea56132fd538c9aa54b671950e1297 Mon Sep 17 00:00:00 2001 From: Matt Mills Date: Thu, 29 Oct 2020 18:28:13 -0600 Subject: Replace swig with pybind11 for gr3.9 master compat Signed-off-by: Eric Wild --- CMakeLists.txt | 16 +- docs/doxygen/pydoc_macros.h | 19 ++ docs/doxygen/swig_doc.py | 332 -------------------- docs/doxygen/update_pydoc.py | 346 +++++++++++++++++++++ python/CMakeLists.txt | 20 +- python/__init__.py | 29 +- python/bindings/CMakeLists.txt | 30 ++ python/bindings/README.md | 0 python/bindings/bind_oot_file.py | 53 ++++ python/bindings/docstrings/README.md | 1 + python/bindings/docstrings/sink_pydoc_template.h | 153 +++++++++ python/bindings/docstrings/source_pydoc_template.h | 162 ++++++++++ python/bindings/header_utils.py | 78 +++++ python/bindings/python_bindings.cc | 57 ++++ python/bindings/sink_python.cc | 320 +++++++++++++++++++ python/bindings/source_python.cc | 342 ++++++++++++++++++++ swig/CMakeLists.txt | 57 ---- swig/osmosdr_swig.i | 82 ----- 18 files changed, 1583 insertions(+), 514 deletions(-) create mode 100644 docs/doxygen/pydoc_macros.h delete mode 100644 docs/doxygen/swig_doc.py create mode 100644 docs/doxygen/update_pydoc.py create mode 100644 python/bindings/CMakeLists.txt create mode 100644 python/bindings/README.md create mode 100644 python/bindings/bind_oot_file.py create mode 100644 python/bindings/docstrings/README.md create mode 100644 python/bindings/docstrings/sink_pydoc_template.h create mode 100644 python/bindings/docstrings/source_pydoc_template.h create mode 100644 python/bindings/header_utils.py create mode 100644 python/bindings/python_bindings.cc create mode 100644 python/bindings/sink_python.cc create mode 100644 python/bindings/source_python.cc delete mode 100644 swig/CMakeLists.txt delete mode 100644 swig/osmosdr_swig.i diff --git a/CMakeLists.txt b/CMakeLists.txt index 74c54f5..b4bb535 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "") list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules) # Find GNURadio (pmt and runtime are core, always included) -find_package(Gnuradio "3.8" REQUIRED COMPONENTS blocks fft filter) +find_package(Gnuradio "3.9" REQUIRED COMPONENTS blocks fft filter) # Set the version information here set(VERSION_MAJOR 0) @@ -188,20 +188,11 @@ find_package(Doxygen) ########## find_package(PythonLibs 3) -find_package(SWIG) - -if(SWIG_FOUND) - message(STATUS "Minimum SWIG version required is 1.3.31") - set(SWIG_VERSION_CHECK FALSE) - if("${SWIG_VERSION}" VERSION_GREATER "1.3.30") - set(SWIG_VERSION_CHECK TRUE) - endif() -endif(SWIG_FOUND) +find_package(pybind11) GR_REGISTER_COMPONENT("Python support" ENABLE_PYTHON PYTHONLIBS_FOUND - SWIG_FOUND - SWIG_VERSION_CHECK + pybind11_FOUND ) ######################################################################## @@ -269,7 +260,6 @@ add_custom_target(uninstall add_subdirectory(include/osmosdr) add_subdirectory(lib) if(ENABLE_PYTHON) - add_subdirectory(swig) add_subdirectory(python) add_subdirectory(grc) add_subdirectory(apps) diff --git a/docs/doxygen/pydoc_macros.h b/docs/doxygen/pydoc_macros.h new file mode 100644 index 0000000..98bf7cd --- /dev/null +++ b/docs/doxygen/pydoc_macros.h @@ -0,0 +1,19 @@ +#ifndef PYDOC_MACROS_H +#define PYDOC_MACROS_H + +#define __EXPAND(x) x +#define __COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT +#define __VA_SIZE(...) __EXPAND(__COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1)) +#define __CAT1(a, b) a##b +#define __CAT2(a, b) __CAT1(a, b) +#define __DOC1(n1) __doc_##n1 +#define __DOC2(n1, n2) __doc_##n1##_##n2 +#define __DOC3(n1, n2, n3) __doc_##n1##_##n2##_##n3 +#define __DOC4(n1, n2, n3, n4) __doc_##n1##_##n2##_##n3##_##n4 +#define __DOC5(n1, n2, n3, n4, n5) __doc_##n1##_##n2##_##n3##_##n4##_##n5 +#define __DOC6(n1, n2, n3, n4, n5, n6) __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6 +#define __DOC7(n1, n2, n3, n4, n5, n6, n7) \ + __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 +#define DOC(...) __EXPAND(__EXPAND(__CAT2(__DOC, __VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) + +#endif // PYDOC_MACROS_H \ No newline at end of file diff --git a/docs/doxygen/swig_doc.py b/docs/doxygen/swig_doc.py deleted file mode 100644 index e3b308e..0000000 --- a/docs/doxygen/swig_doc.py +++ /dev/null @@ -1,332 +0,0 @@ -# -# Copyright 2010-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-osmosdr -# -# 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. -# -""" -Creates the swig_doc.i SWIG interface file. -Execute using: python swig_doc.py xml_path outputfilename - -The file instructs SWIG to transfer the doxygen comments into the -python docstrings. - -""" -from __future__ import unicode_literals - -import sys, time - -from doxyxml import DoxyIndex, DoxyClass, DoxyFriend, DoxyFunction, DoxyFile -from doxyxml import DoxyOther, base - -def py_name(name): - bits = name.split('_') - return '_'.join(bits[1:]) - -def make_name(name): - bits = name.split('_') - return bits[0] + '_make_' + '_'.join(bits[1:]) - - -class Block(object): - """ - Checks if doxyxml produced objects correspond to a gnuradio block. - """ - - @classmethod - def includes(cls, item): - if not isinstance(item, DoxyClass): - return False - # Check for a parsing error. - if item.error(): - return False - friendname = make_name(item.name()) - is_a_block = item.has_member(friendname, DoxyFriend) - # But now sometimes the make function isn't a friend so check again. - if not is_a_block: - is_a_block = di.has_member(friendname, DoxyFunction) - return is_a_block - -class Block2(object): - """ - Checks if doxyxml produced objects correspond to a new style - gnuradio block. - """ - - @classmethod - def includes(cls, item): - if not isinstance(item, DoxyClass): - return False - # Check for a parsing error. - if item.error(): - return False - is_a_block2 = item.has_member('make', DoxyFunction) and item.has_member('sptr', DoxyOther) - return is_a_block2 - - -def utoascii(text): - """ - Convert unicode text into ascii and escape quotes and backslashes. - """ - if text is None: - return '' - out = text.encode('ascii', 'replace') - # swig will require us to replace blackslash with 4 backslashes - out = out.replace(b'\\', b'\\\\\\\\') - out = out.replace(b'"', b'\\"').decode('ascii') - return str(out) - - -def combine_descriptions(obj): - """ - Combines the brief and detailed descriptions of an object together. - """ - description = [] - bd = obj.brief_description.strip() - dd = obj.detailed_description.strip() - if bd: - description.append(bd) - if dd: - description.append(dd) - return utoascii('\n\n'.join(description)).strip() - -def format_params(parameteritems): - output = ['Args:'] - template = ' {0} : {1}' - for pi in parameteritems: - output.append(template.format(pi.name, pi.description)) - return '\n'.join(output) - -entry_templ = '%feature("docstring") {name} "{docstring}"' -def make_entry(obj, name=None, templ="{description}", description=None, params=[]): - """ - Create a docstring entry for a swig interface file. - - obj - a doxyxml object from which documentation will be extracted. - name - the name of the C object (defaults to obj.name()) - templ - an optional template for the docstring containing only one - variable named 'description'. - description - if this optional variable is set then it's value is - used as the description instead of extracting it from obj. - """ - if name is None: - name=obj.name() - if "operator " in name: - return '' - if description is None: - description = combine_descriptions(obj) - if params: - description += '\n\n' - description += utoascii(format_params(params)) - docstring = templ.format(description=description) - if not docstring: - return '' - return entry_templ.format( - name=name, - docstring=docstring, - ) - - -def make_func_entry(func, name=None, description=None, params=None): - """ - Create a function docstring entry for a swig interface file. - - func - a doxyxml object from which documentation will be extracted. - name - the name of the C object (defaults to func.name()) - description - if this optional variable is set then it's value is - used as the description instead of extracting it from func. - params - a parameter list that overrides using func.params. - """ - #if params is None: - # params = func.params - #params = [prm.declname for prm in params] - #if params: - # sig = "Params: (%s)" % ", ".join(params) - #else: - # sig = "Params: (NONE)" - #templ = "{description}\n\n" + sig - #return make_entry(func, name=name, templ=utoascii(templ), - # description=description) - return make_entry(func, name=name, description=description, params=params) - - -def make_class_entry(klass, description=None, ignored_methods=[], params=None): - """ - Create a class docstring for a swig interface file. - """ - if params is None: - params = klass.params - output = [] - output.append(make_entry(klass, description=description, params=params)) - for func in klass.in_category(DoxyFunction): - if func.name() not in ignored_methods: - name = klass.name() + '::' + func.name() - output.append(make_func_entry(func, name=name)) - return "\n\n".join(output) - - -def make_block_entry(di, block): - """ - Create class and function docstrings of a gnuradio block for a - swig interface file. - """ - descriptions = [] - # Get the documentation associated with the class. - class_desc = combine_descriptions(block) - if class_desc: - descriptions.append(class_desc) - # Get the documentation associated with the make function - make_func = di.get_member(make_name(block.name()), DoxyFunction) - make_func_desc = combine_descriptions(make_func) - if make_func_desc: - descriptions.append(make_func_desc) - # Get the documentation associated with the file - try: - block_file = di.get_member(block.name() + ".h", DoxyFile) - file_desc = combine_descriptions(block_file) - if file_desc: - descriptions.append(file_desc) - except base.Base.NoSuchMember: - # Don't worry if we can't find a matching file. - pass - # And join them all together to make a super duper description. - super_description = "\n\n".join(descriptions) - # Associate the combined description with the class and - # the make function. - output = [] - output.append(make_class_entry(block, description=super_description)) - output.append(make_func_entry(make_func, description=super_description, - params=block.params)) - return "\n\n".join(output) - -def make_block2_entry(di, block): - """ - Create class and function docstrings of a new style gnuradio block for a - swig interface file. - """ - descriptions = [] - # For new style blocks all the relevant documentation should be - # associated with the 'make' method. - class_description = combine_descriptions(block) - make_func = block.get_member('make', DoxyFunction) - make_description = combine_descriptions(make_func) - description = class_description + "\n\nConstructor Specific Documentation:\n\n" + make_description - # Associate the combined description with the class and - # the make function. - output = [] - output.append(make_class_entry( - block, description=description, - ignored_methods=['make'], params=make_func.params)) - makename = block.name() + '::make' - output.append(make_func_entry( - make_func, name=makename, description=description, - params=make_func.params)) - return "\n\n".join(output) - -def make_swig_interface_file(di, swigdocfilename, custom_output=None): - - output = [""" -/* - * This file was automatically generated using swig_doc.py. - * - * Any changes to it will be lost next time it is regenerated. - */ -"""] - - if custom_output is not None: - output.append(custom_output) - - # Create docstrings for the blocks. - blocks = di.in_category(Block) - blocks2 = di.in_category(Block2) - - make_funcs = set([]) - for block in blocks: - try: - make_func = di.get_member(make_name(block.name()), DoxyFunction) - # Don't want to risk writing to output twice. - if make_func.name() not in make_funcs: - make_funcs.add(make_func.name()) - output.append(make_block_entry(di, block)) - except block.ParsingError: - sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) - raise - - for block in blocks2: - try: - make_func = block.get_member('make', DoxyFunction) - make_func_name = block.name() +'::make' - # Don't want to risk writing to output twice. - if make_func_name not in make_funcs: - make_funcs.add(make_func_name) - output.append(make_block2_entry(di, block)) - except block.ParsingError: - sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) - raise - - # Create docstrings for functions - # Don't include the make functions since they have already been dealt with. - funcs = [f for f in di.in_category(DoxyFunction) - if f.name() not in make_funcs and not f.name().startswith('std::')] - for f in funcs: - try: - output.append(make_func_entry(f)) - except f.ParsingError: - sys.stderr.write('Parsing error for function {0}\n'.format(f.name())) - - # Create docstrings for classes - block_names = [block.name() for block in blocks] - block_names += [block.name() for block in blocks2] - klasses = [k for k in di.in_category(DoxyClass) - if k.name() not in block_names and not k.name().startswith('std::')] - for k in klasses: - try: - output.append(make_class_entry(k)) - except k.ParsingError: - sys.stderr.write('Parsing error for class {0}\n'.format(k.name())) - - # Docstrings are not created for anything that is not a function or a class. - # If this excludes anything important please add it here. - - output = "\n\n".join(output) - - swig_doc = open(swigdocfilename, 'w') - swig_doc.write(output) - swig_doc.close() - -if __name__ == "__main__": - # Parse command line options and set up doxyxml. - err_msg = "Execute using: python swig_doc.py xml_path outputfilename" - if len(sys.argv) != 3: - raise Exception(err_msg) - xml_path = sys.argv[1] - swigdocfilename = sys.argv[2] - di = DoxyIndex(xml_path) - - # gnuradio.gr.msq_queue.insert_tail and delete_head create errors unless docstrings are defined! - # This is presumably a bug in SWIG. - #msg_q = di.get_member(u'gr_msg_queue', DoxyClass) - #insert_tail = msg_q.get_member(u'insert_tail', DoxyFunction) - #delete_head = msg_q.get_member(u'delete_head', DoxyFunction) - output = [] - #output.append(make_func_entry(insert_tail, name='gr_py_msg_queue__insert_tail')) - #output.append(make_func_entry(delete_head, name='gr_py_msg_queue__delete_head')) - custom_output = "\n\n".join(output) - - # Generate the docstrings interface file. - make_swig_interface_file(di, swigdocfilename, custom_output=custom_output) diff --git a/docs/doxygen/update_pydoc.py b/docs/doxygen/update_pydoc.py new file mode 100644 index 0000000..e6b4544 --- /dev/null +++ b/docs/doxygen/update_pydoc.py @@ -0,0 +1,346 @@ +# +# Copyright 2010-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 gnuradio +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# +""" +Updates the *pydoc_h files for a module +Execute using: python update_pydoc.py xml_path outputfilename + +The file instructs Pybind11 to transfer the doxygen comments into the +python docstrings. + +""" + +import os, sys, time, glob, re, json +from argparse import ArgumentParser + +from doxyxml import DoxyIndex, DoxyClass, DoxyFriend, DoxyFunction, DoxyFile +from doxyxml import DoxyOther, base + +def py_name(name): + bits = name.split('_') + return '_'.join(bits[1:]) + +def make_name(name): + bits = name.split('_') + return bits[0] + '_make_' + '_'.join(bits[1:]) + + +class Block(object): + """ + Checks if doxyxml produced objects correspond to a gnuradio block. + """ + + @classmethod + def includes(cls, item): + if not isinstance(item, DoxyClass): + return False + # Check for a parsing error. + if item.error(): + return False + friendname = make_name(item.name()) + is_a_block = item.has_member(friendname, DoxyFriend) + # But now sometimes the make function isn't a friend so check again. + if not is_a_block: + is_a_block = di.has_member(friendname, DoxyFunction) + return is_a_block + +class Block2(object): + """ + Checks if doxyxml produced objects correspond to a new style + gnuradio block. + """ + + @classmethod + def includes(cls, item): + if not isinstance(item, DoxyClass): + return False + # Check for a parsing error. + if item.error(): + return False + is_a_block2 = item.has_member('make', DoxyFunction) and item.has_member('sptr', DoxyOther) + return is_a_block2 + + +def utoascii(text): + """ + Convert unicode text into ascii and escape quotes and backslashes. + """ + if text is None: + return '' + out = text.encode('ascii', 'replace') + # swig will require us to replace blackslash with 4 backslashes + # TODO: evaluate what this should be for pybind11 + out = out.replace(b'\\', b'\\\\\\\\') + out = out.replace(b'"', b'\\"').decode('ascii') + return str(out) + + +def combine_descriptions(obj): + """ + Combines the brief and detailed descriptions of an object together. + """ + description = [] + bd = obj.brief_description.strip() + dd = obj.detailed_description.strip() + if bd: + description.append(bd) + if dd: + description.append(dd) + return utoascii('\n\n'.join(description)).strip() + +def format_params(parameteritems): + output = ['Args:'] + template = ' {0} : {1}' + for pi in parameteritems: + output.append(template.format(pi.name, pi.description)) + return '\n'.join(output) + +entry_templ = '%feature("docstring") {name} "{docstring}"' +def make_entry(obj, name=None, templ="{description}", description=None, params=[]): + """ + Create a docstring key/value pair, where the key is the object name. + + obj - a doxyxml object from which documentation will be extracted. + name - the name of the C object (defaults to obj.name()) + templ - an optional template for the docstring containing only one + variable named 'description'. + description - if this optional variable is set then it's value is + used as the description instead of extracting it from obj. + """ + if name is None: + name=obj.name() + if hasattr(obj,'_parse_data') and hasattr(obj._parse_data,'definition'): + name=obj._parse_data.definition.split(' ')[-1] + if "operator " in name: + return '' + if description is None: + description = combine_descriptions(obj) + if params: + description += '\n\n' + description += utoascii(format_params(params)) + docstring = templ.format(description=description) + + return {name: docstring} + + +def make_class_entry(klass, description=None, ignored_methods=[], params=None): + """ + Create a class docstring key/value pair. + """ + if params is None: + params = klass.params + output = {} + output.update(make_entry(klass, description=description, params=params)) + for func in klass.in_category(DoxyFunction): + if func.name() not in ignored_methods: + name = klass.name() + '::' + func.name() + output.update(make_entry(func, name=name)) + return output + + +def make_block_entry(di, block): + """ + Create class and function docstrings of a gnuradio block + """ + descriptions = [] + # Get the documentation associated with the class. + class_desc = combine_descriptions(block) + if class_desc: + descriptions.append(class_desc) + # Get the documentation associated with the make function + make_func = di.get_member(make_name(block.name()), DoxyFunction) + make_func_desc = combine_descriptions(make_func) + if make_func_desc: + descriptions.append(make_func_desc) + # Get the documentation associated with the file + try: + block_file = di.get_member(block.name() + ".h", DoxyFile) + file_desc = combine_descriptions(block_file) + if file_desc: + descriptions.append(file_desc) + except base.Base.NoSuchMember: + # Don't worry if we can't find a matching file. + pass + # And join them all together to make a super duper description. + super_description = "\n\n".join(descriptions) + # Associate the combined description with the class and + # the make function. + output = {} + output.update(make_class_entry(block, description=super_description)) + output.update(make_entry(make_func, description=super_description, + params=block.params)) + return output + +def make_block2_entry(di, block): + """ + Create class and function docstrings of a new style gnuradio block + """ + # For new style blocks all the relevant documentation should be + # associated with the 'make' method. + class_description = combine_descriptions(block) + make_func = block.get_member('make', DoxyFunction) + make_description = combine_descriptions(make_func) + description = class_description + "\n\nConstructor Specific Documentation:\n\n" + make_description + # Associate the combined description with the class and + # the make function. + output = {} + output.update(make_class_entry( + block, description=description, + ignored_methods=['make'], params=make_func.params)) + makename = block.name() + '::make' + output.update(make_entry( + make_func, name=makename, description=description, + params=make_func.params)) + return output + +def get_docstrings_dict(di, custom_output=None): + + output = {} + if custom_output: + output.update(custom_output) + + # Create docstrings for the blocks. + blocks = di.in_category(Block) + blocks2 = di.in_category(Block2) + + make_funcs = set([]) + for block in blocks: + try: + make_func = di.get_member(make_name(block.name()), DoxyFunction) + # Don't want to risk writing to output twice. + if make_func.name() not in make_funcs: + make_funcs.add(make_func.name()) + output.update(make_block_entry(di, block)) + except block.ParsingError: + sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) + raise + + for block in blocks2: + try: + make_func = block.get_member('make', DoxyFunction) + make_func_name = block.name() +'::make' + # Don't want to risk writing to output twice. + if make_func_name not in make_funcs: + make_funcs.add(make_func_name) + output.update(make_block2_entry(di, block)) + except block.ParsingError: + sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) + raise + + # Create docstrings for functions + # Don't include the make functions since they have already been dealt with. + funcs = [f for f in di.in_category(DoxyFunction) + if f.name() not in make_funcs and not f.name().startswith('std::')] + for f in funcs: + try: + output.update(make_entry(f)) + except f.ParsingError: + sys.stderr.write('Parsing error for function {0}\n'.format(f.name())) + + # Create docstrings for classes + block_names = [block.name() for block in blocks] + block_names += [block.name() for block in blocks2] + klasses = [k for k in di.in_category(DoxyClass) + if k.name() not in block_names and not k.name().startswith('std::')] + for k in klasses: + try: + output.update(make_class_entry(k)) + except k.ParsingError: + sys.stderr.write('Parsing error for class {0}\n'.format(k.name())) + + # Docstrings are not created for anything that is not a function or a class. + # If this excludes anything important please add it here. + + return output + +def sub_docstring_in_pydoc_h(pydoc_files, docstrings_dict, output_dir, filter_str=None): + if filter_str: + docstrings_dict = {k: v for k, v in docstrings_dict.items() if k.startswith(filter_str)} + + with open(os.path.join(output_dir,'docstring_status'),'w') as status_file: + + for pydoc_file in pydoc_files: + if filter_str: + filter_str2 = "::".join((filter_str,os.path.split(pydoc_file)[-1].split('_pydoc_template.h')[0])) + docstrings_dict2 = {k: v for k, v in docstrings_dict.items() if k.startswith(filter_str2)} + else: + docstrings_dict2 = docstrings_dict + + + + file_in = open(pydoc_file,'r').read() + for key, value in docstrings_dict2.items(): + file_in_tmp = file_in + try: + doc_key = key.split("::") + # if 'gr' in doc_key: + # doc_key.remove('gr') + doc_key = '_'.join(doc_key) + regexp = r'(__doc_{} =\sR\"doc\()[^)]*(\)doc\")'.format(doc_key) + regexp = re.compile(regexp, re.MULTILINE) + + (file_in, nsubs) = regexp.subn(r'\1'+value+r'\2', file_in, count=1) + if nsubs == 1: + status_file.write("PASS: " + pydoc_file + "\n") + except KeyboardInterrupt: + raise KeyboardInterrupt + except: # be permissive, TODO log, but just leave the docstring blank + status_file.write("FAIL: " + pydoc_file + "\n") + file_in = file_in_tmp + + output_pathname = os.path.join(output_dir, os.path.basename(pydoc_file).replace('_template.h','.h')) + # FIXME: Remove this debug print + print('output docstrings to {}'.format(output_pathname)) + with open(output_pathname,'w') as file_out: + file_out.write(file_in) + +def copy_docstring_templates(pydoc_files, output_dir): + with open(os.path.join(output_dir,'docstring_status'),'w') as status_file: + for pydoc_file in pydoc_files: + file_in = open(pydoc_file,'r').read() + output_pathname = os.path.join(output_dir, os.path.basename(pydoc_file).replace('_template.h','.h')) + # FIXME: Remove this debug print + print('copy docstrings to {}'.format(output_pathname)) + with open(output_pathname,'w') as file_out: + file_out.write(file_in) + status_file.write("DONE") + +def argParse(): + """Parses commandline args.""" + desc='Scrape the doxygen generated xml for docstrings to insert into python bindings' + parser = ArgumentParser(description=desc) + + parser.add_argument("function", help="Operation to perform on docstrings", choices=["scrape","sub","copy"]) + + parser.add_argument("--xml_path") + parser.add_argument("--bindings_dir") + parser.add_argument("--output_dir") + parser.add_argument("--json_path") + parser.add_argument("--filter", default=None) + + return parser.parse_args() + +if __name__ == "__main__": + # Parse command line options and set up doxyxml. + args = argParse() + if args.function.lower() == 'scrape': + di = DoxyIndex(args.xml_path) + docstrings_dict = get_docstrings_dict(di) + with open(args.json_path, 'w') as fp: + json.dump(docstrings_dict, fp) + elif args.function.lower() == 'sub': + with open(args.json_path, 'r') as fp: + docstrings_dict = json.load(fp) + pydoc_files = glob.glob(os.path.join(args.bindings_dir,'*_pydoc_template.h')) + sub_docstring_in_pydoc_h(pydoc_files, docstrings_dict, args.output_dir, args.filter) + elif args.function.lower() == 'copy': + pydoc_files = glob.glob(os.path.join(args.bindings_dir,'*_pydoc_template.h')) + copy_docstring_templates(pydoc_files, args.output_dir) + + diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 53cb61e..dcae02a 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,21 +1,10 @@ # Copyright 2011 Free Software Foundation, Inc. # -# This file is part of gr-osmosdr +# This file was generated by gr_modtool, a tool from the GNU Radio framework +# This file is a part of gr-osmosdr # -# gr-osmosdr 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. +# SPDX-License-Identifier: GPL-3.0-or-later # -# gr-osmosdr 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 gr-osmosdr; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. ######################################################################## # Include python install macros @@ -25,6 +14,8 @@ if(NOT PYTHONINTERP_FOUND) return() endif() +add_subdirectory(bindings) + ######################################################################## # Install python sources ######################################################################## @@ -40,4 +31,3 @@ GR_PYTHON_INSTALL( include(GrTest) set(GR_TEST_TARGET_DEPS gnuradio-osmosdr) -set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig) diff --git a/python/__init__.py b/python/__init__.py index 1cb090f..e619f4f 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -1,25 +1,24 @@ # # Copyright 2008,2009 Free Software Foundation, Inc. # -# This application is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# This application is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# SPDX-License-Identifier: GPL-3.0-or-later # # The presence of this file turns this directory into a Python package ''' -This is the GNU Radio OsmoSDR module. +This is the GNU Radio OSMOSDR module. Place your Python package +description here (python/__init__.py). ''' +import os -from .osmosdr_swig import * +# import pybind11 generated symbols into the osmosdr namespace +try: + from .osmosdr_python import * +except ImportError: + dirname, filename = os.path.split(os.path.abspath(__file__)) + __path__.append(os.path.join(dirname, "bindings")) + from .osmosdr_python import * + +# import any pure python here +# diff --git a/python/bindings/CMakeLists.txt b/python/bindings/CMakeLists.txt new file mode 100644 index 0000000..ed50d7e --- /dev/null +++ b/python/bindings/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright 2020 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +GR_PYTHON_CHECK_MODULE_RAW( + "pygccxml" + "import pygccxml" + PYGCCXML_FOUND + ) + +include(GrPybind) + +######################################################################## +# Python Bindings +######################################################################## + +list(APPEND osmosdr_python_files + sink_python.cc + source_python.cc + python_bindings.cc) + +GR_PYBIND_MAKE_OOT(osmosdr + ../.. + gr::osmosdr + "${osmosdr_python_files}") + +install(TARGETS osmosdr_python DESTINATION ${GR_PYTHON_DIR}/osmosdr COMPONENT pythonapi) diff --git a/python/bindings/README.md b/python/bindings/README.md new file mode 100644 index 0000000..e69de29 diff --git a/python/bindings/bind_oot_file.py b/python/bindings/bind_oot_file.py new file mode 100644 index 0000000..55de795 --- /dev/null +++ b/python/bindings/bind_oot_file.py @@ -0,0 +1,53 @@ +import warnings +import argparse +import os +from gnuradio.bindtool import BindingGenerator +import pathlib +import sys + +parser = argparse.ArgumentParser(description='Bind a GR Out of Tree Block') +parser.add_argument('--module', type=str, + help='Name of gr module containing file to bind (e.g. fft digital analog)') + +parser.add_argument('--output_dir', default='/tmp', + help='Output directory of generated bindings') +parser.add_argument('--prefix', help='Prefix of Installed GNU Radio') +parser.add_argument('--src', help='Directory of gnuradio source tree', + default=os.path.dirname(os.path.abspath(__file__))+'/../../..') + +parser.add_argument( + '--filename', help="File to be parsed") + +parser.add_argument( + '--include', help='Additional Include Dirs, separated', default=(), nargs='+') + +parser.add_argument( + '--status', help='Location of output file for general status (used during cmake)', default=None +) +parser.add_argument( + '--flag_automatic', default='0' +) +parser.add_argument( + '--flag_pygccxml', default='0' +) + +args = parser.parse_args() + +prefix = args.prefix +output_dir = args.output_dir +includes = args.include +name = args.module + +namespace = [name] +prefix_include_root = name + + +with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + + bg = BindingGenerator(prefix, namespace, + prefix_include_root, output_dir, addl_includes=','.join(args.include), catch_exceptions=False, write_json_output=False, status_output=args.status, + flag_automatic=True if args.flag_automatic.lower() in [ + '1', 'true'] else False, + flag_pygccxml=True if args.flag_pygccxml.lower() in ['1', 'true'] else False) + bg.gen_file_binding(args.filename) diff --git a/python/bindings/docstrings/README.md b/python/bindings/docstrings/README.md new file mode 100644 index 0000000..295455a --- /dev/null +++ b/python/bindings/docstrings/README.md @@ -0,0 +1 @@ +This directory stores templates for docstrings that are scraped from the include header files for each block \ No newline at end of file diff --git a/python/bindings/docstrings/sink_pydoc_template.h b/python/bindings/docstrings/sink_pydoc_template.h new file mode 100644 index 0000000..dad47cf --- /dev/null +++ b/python/bindings/docstrings/sink_pydoc_template.h @@ -0,0 +1,153 @@ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr,osmosdr, __VA_ARGS__ ) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ + + + + static const char *__doc_osmosdr_sink = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_sink_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_sink_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_make = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_num_channels = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_sample_rates = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_sample_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_sample_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_freq_range = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_center_freq = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_center_freq = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_freq_corr = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_freq_corr = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_gain_names = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_gain_range_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_gain_range_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_gain_mode = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_gain_mode = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_gain_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_gain_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_gain_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_gain_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_if_gain = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_bb_gain = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_antennas = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_antenna = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_antenna = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_dc_offset = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_iq_balance = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_bandwidth = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_bandwidth = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_bandwidth_range = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_time_source = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_time_source = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_time_sources = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_clock_source = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_clock_source = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_clock_sources = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_clock_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_clock_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_time_now = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_get_time_last_pps = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_time_now = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_time_next_pps = R"doc()doc"; + + + static const char *__doc_osmosdr_sink_set_time_unknown_pps = R"doc()doc"; + + diff --git a/python/bindings/docstrings/source_pydoc_template.h b/python/bindings/docstrings/source_pydoc_template.h new file mode 100644 index 0000000..f2c3ca4 --- /dev/null +++ b/python/bindings/docstrings/source_pydoc_template.h @@ -0,0 +1,162 @@ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr,osmosdr, __VA_ARGS__ ) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ + + + + static const char *__doc_osmosdr_source = R"doc()doc"; + + + static const char *__doc_osmosdr_source_source_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_source_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_make = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_num_channels = R"doc()doc"; + + + static const char *__doc_osmosdr_source_seek = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_sample_rates = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_sample_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_sample_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_freq_range = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_center_freq = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_center_freq = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_freq_corr = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_freq_corr = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_gain_names = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_gain_range_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_gain_range_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_gain_mode = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_gain_mode = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_gain_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_gain_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_gain_0 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_gain_1 = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_if_gain = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_bb_gain = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_antennas = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_antenna = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_antenna = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_dc_offset_mode = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_dc_offset = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_iq_balance_mode = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_iq_balance = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_bandwidth = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_bandwidth = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_bandwidth_range = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_time_source = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_time_source = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_time_sources = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_clock_source = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_clock_source = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_clock_sources = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_clock_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_clock_rate = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_time_now = R"doc()doc"; + + + static const char *__doc_osmosdr_source_get_time_last_pps = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_time_now = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_time_next_pps = R"doc()doc"; + + + static const char *__doc_osmosdr_source_set_time_unknown_pps = R"doc()doc"; + + diff --git a/python/bindings/header_utils.py b/python/bindings/header_utils.py new file mode 100644 index 0000000..165124e --- /dev/null +++ b/python/bindings/header_utils.py @@ -0,0 +1,78 @@ +# Utilities for reading values in header files + +from argparse import ArgumentParser +import re + + +class PybindHeaderParser: + def __init__(self, pathname): + with open(pathname,'r') as f: + self.file_txt = f.read() + + def get_flag_automatic(self): + # p = re.compile(r'BINDTOOL_GEN_AUTOMATIC\(([^\s])\)') + # m = p.search(self.file_txt) + m = re.search(r'BINDTOOL_GEN_AUTOMATIC\(([^\s])\)', self.file_txt) + if (m and m.group(1) == '1'): + return True + else: + return False + + def get_flag_pygccxml(self): + # p = re.compile(r'BINDTOOL_USE_PYGCCXML\(([^\s])\)') + # m = p.search(self.file_txt) + m = re.search(r'BINDTOOL_USE_PYGCCXML\(([^\s])\)', self.file_txt) + if (m and m.group(1) == '1'): + return True + else: + return False + + def get_header_filename(self): + # p = re.compile(r'BINDTOOL_HEADER_FILE\(([^\s]*)\)') + # m = p.search(self.file_txt) + m = re.search(r'BINDTOOL_HEADER_FILE\(([^\s]*)\)', self.file_txt) + if (m): + return m.group(1) + else: + return None + + def get_header_file_hash(self): + # p = re.compile(r'BINDTOOL_HEADER_FILE_HASH\(([^\s]*)\)') + # m = p.search(self.file_txt) + m = re.search(r'BINDTOOL_HEADER_FILE_HASH\(([^\s]*)\)', self.file_txt) + if (m): + return m.group(1) + else: + return None + + def get_flags(self): + return f'{self.get_flag_automatic()};{self.get_flag_pygccxml()};{self.get_header_filename()};{self.get_header_file_hash()};' + + + +def argParse(): + """Parses commandline args.""" + desc='Reads the parameters from the comment block in the pybind files' + parser = ArgumentParser(description=desc) + + parser.add_argument("function", help="Operation to perform on comment block of pybind file", choices=["flag_auto","flag_pygccxml","header_filename","header_file_hash","all"]) + parser.add_argument("pathname", help="Pathname of pybind c++ file to read, e.g. blockname_python.cc") + + return parser.parse_args() + +if __name__ == "__main__": + # Parse command line options and set up doxyxml. + args = argParse() + + pbhp = PybindHeaderParser(args.pathname) + + if args.function == "flag_auto": + print(pbhp.get_flag_automatic()) + elif args.function == "flag_pygccxml": + print(pbhp.get_flag_pygccxml()) + elif args.function == "header_filename": + print(pbhp.get_header_filename()) + elif args.function == "header_file_hash": + print(pbhp.get_header_file_hash()) + elif args.function == "all": + print(pbhp.get_flags()) \ No newline at end of file diff --git a/python/bindings/python_bindings.cc b/python/bindings/python_bindings.cc new file mode 100644 index 0000000..fc11ee0 --- /dev/null +++ b/python/bindings/python_bindings.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include + +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include + +namespace py = pybind11; + +// Headers for binding functions +/**************************************/ +/* The following comment block is used for +/* gr_modtool to insert function prototypes +/* Please do not delete +/**************************************/ +// BINDING_FUNCTION_PROTOTYPES( + void bind_sink(py::module& m); + void bind_source(py::module& m); +// ) END BINDING_FUNCTION_PROTOTYPES + + +// We need this hack because import_array() returns NULL +// for newer Python versions. +// This function is also necessary because it ensures access to the C API +// and removes a warning. +void* init_numpy() +{ + import_array(); + return NULL; +} + +PYBIND11_MODULE(osmosdr_python, m) +{ + // Initialize the numpy C API + // (otherwise we will see segmentation faults) + init_numpy(); + + // Allow access to base block methods + py::module::import("gnuradio.gr"); + + /**************************************/ + /* The following comment block is used for + /* gr_modtool to insert binding function calls + /* Please do not delete + /**************************************/ + // BINDING_FUNCTION_CALLS( + bind_sink(m); + bind_source(m); + // ) END BINDING_FUNCTION_CALLS +} diff --git a/python/bindings/sink_python.cc b/python/bindings/sink_python.cc new file mode 100644 index 0000000..1c6711d --- /dev/null +++ b/python/bindings/sink_python.cc @@ -0,0 +1,320 @@ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(1) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(sink.h) */ +/* BINDTOOL_HEADER_FILE_HASH(d4331eb8a19b7a2aa4ed0100039f7a0e) */ +/***********************************************************************************/ + +#include +#include +#include + +namespace py = pybind11; + +#include +// pydoc.h is automatically generated in the build directory +#include + +void bind_sink(py::module& m) +{ + + using sink = ::osmosdr::sink; + + + py::class_>(m, "sink", D(sink)) + + .def(py::init(&sink::make), + py::arg("args") = "", + D(sink,make) + ) + + + + + + .def("get_num_channels",&sink::get_num_channels, + D(sink,get_num_channels) + ) + + + .def("get_sample_rates",&sink::get_sample_rates, + D(sink,get_sample_rates) + ) + + + .def("set_sample_rate",&sink::set_sample_rate, + py::arg("rate"), + D(sink,set_sample_rate) + ) + + + .def("get_sample_rate",&sink::get_sample_rate, + D(sink,get_sample_rate) + ) + + + .def("get_freq_range",&sink::get_freq_range, + py::arg("chan") = 0, + D(sink,get_freq_range) + ) + + + .def("set_center_freq",&sink::set_center_freq, + py::arg("freq"), + py::arg("chan") = 0, + D(sink,set_center_freq) + ) + + + .def("get_center_freq",&sink::get_center_freq, + py::arg("chan") = 0, + D(sink,get_center_freq) + ) + + + .def("set_freq_corr",&sink::set_freq_corr, + py::arg("ppm"), + py::arg("chan") = 0, + D(sink,set_freq_corr) + ) + + + .def("get_freq_corr",&sink::get_freq_corr, + py::arg("chan") = 0, + D(sink,get_freq_corr) + ) + + + .def("get_gain_names",&sink::get_gain_names, + py::arg("chan") = 0, + D(sink,get_gain_names) + ) + + + .def("get_gain_range",(osmosdr::gain_range_t (sink::*)(size_t))&sink::get_gain_range, + py::arg("chan") = 0, + D(sink,get_gain_range,0) + ) + + + .def("get_gain_range",(osmosdr::gain_range_t (sink::*)(std::string const &, size_t))&sink::get_gain_range, + py::arg("name"), + py::arg("chan") = 0, + D(sink,get_gain_range,1) + ) + + + .def("set_gain_mode",&sink::set_gain_mode, + py::arg("automatic"), + py::arg("chan") = 0, + D(sink,set_gain_mode) + ) + + + .def("get_gain_mode",&sink::get_gain_mode, + py::arg("chan") = 0, + D(sink,get_gain_mode) + ) + + + .def("set_gain",(double (sink::*)(double, size_t))&sink::set_gain, + py::arg("gain"), + py::arg("chan") = 0, + D(sink,set_gain,0) + ) + + + .def("set_gain",(double (sink::*)(double, std::string const &, size_t))&sink::set_gain, + py::arg("gain"), + py::arg("name"), + py::arg("chan") = 0, + D(sink,set_gain,1) + ) + + + .def("get_gain",(double (sink::*)(size_t))&sink::get_gain, + py::arg("chan") = 0, + D(sink,get_gain,0) + ) + + + .def("get_gain",(double (sink::*)(std::string const &, size_t))&sink::get_gain, + py::arg("name"), + py::arg("chan") = 0, + D(sink,get_gain,1) + ) + + + .def("set_if_gain",&sink::set_if_gain, + py::arg("gain"), + py::arg("chan") = 0, + D(sink,set_if_gain) + ) + + + .def("set_bb_gain",&sink::set_bb_gain, + py::arg("gain"), + py::arg("chan") = 0, + D(sink,set_bb_gain) + ) + + + .def("get_antennas",&sink::get_antennas, + py::arg("chan") = 0, + D(sink,get_antennas) + ) + + + .def("set_antenna",&sink::set_antenna, + py::arg("antenna"), + py::arg("chan") = 0, + D(sink,set_antenna) + ) + + + .def("get_antenna",&sink::get_antenna, + py::arg("chan") = 0, + D(sink,get_antenna) + ) + + + .def("set_dc_offset",&sink::set_dc_offset, + py::arg("offset"), + py::arg("chan") = 0, + D(sink,set_dc_offset) + ) + + + .def("set_iq_balance",&sink::set_iq_balance, + py::arg("balance"), + py::arg("chan") = 0, + D(sink,set_iq_balance) + ) + + + .def("set_bandwidth",&sink::set_bandwidth, + py::arg("bandwidth"), + py::arg("chan") = 0, + D(sink,set_bandwidth) + ) + + + .def("get_bandwidth",&sink::get_bandwidth, + py::arg("chan") = 0, + D(sink,get_bandwidth) + ) + + + .def("get_bandwidth_range",&sink::get_bandwidth_range, + py::arg("chan") = 0, + D(sink,get_bandwidth_range) + ) + + + .def("set_time_source",&sink::set_time_source, + py::arg("source"), + py::arg("mboard") = 0, + D(sink,set_time_source) + ) + + + .def("get_time_source",&sink::get_time_source, + py::arg("mboard"), + D(sink,get_time_source) + ) + + + .def("get_time_sources",&sink::get_time_sources, + py::arg("mboard"), + D(sink,get_time_sources) + ) + + + .def("set_clock_source",&sink::set_clock_source, + py::arg("source"), + py::arg("mboard") = 0, + D(sink,set_clock_source) + ) + + + .def("get_clock_source",&sink::get_clock_source, + py::arg("mboard"), + D(sink,get_clock_source) + ) + + + .def("get_clock_sources",&sink::get_clock_sources, + py::arg("mboard"), + D(sink,get_clock_sources) + ) + + + .def("get_clock_rate",&sink::get_clock_rate, + py::arg("mboard") = 0, + D(sink,get_clock_rate) + ) + + + .def("set_clock_rate",&sink::set_clock_rate, + py::arg("rate"), + py::arg("mboard") = 0, + D(sink,set_clock_rate) + ) + + + .def("get_time_now",&sink::get_time_now, + py::arg("mboard") = 0, + D(sink,get_time_now) + ) + + + .def("get_time_last_pps",&sink::get_time_last_pps, + py::arg("mboard") = 0, + D(sink,get_time_last_pps) + ) + + + .def("set_time_now",&sink::set_time_now, + py::arg("time_spec"), + py::arg("mboard") = 0, + D(sink,set_time_now) + ) + + + .def("set_time_next_pps",&sink::set_time_next_pps, + py::arg("time_spec"), + D(sink,set_time_next_pps) + ) + + + .def("set_time_unknown_pps",&sink::set_time_unknown_pps, + py::arg("time_spec"), + D(sink,set_time_unknown_pps) + ) + + ; + + + + +} + + + + + + + diff --git a/python/bindings/source_python.cc b/python/bindings/source_python.cc new file mode 100644 index 0000000..48bf10c --- /dev/null +++ b/python/bindings/source_python.cc @@ -0,0 +1,342 @@ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(1) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(source.h) */ +/* BINDTOOL_HEADER_FILE_HASH(574373c3c7682569b0fd7eea577739da) */ +/***********************************************************************************/ + +#include +#include +#include + +namespace py = pybind11; + +#include +// pydoc.h is automatically generated in the build directory +#include + +void bind_source(py::module& m) +{ + + using source = ::osmosdr::source; + + + py::class_>(m, "source", D(source)) + + .def(py::init(&source::make), + py::arg("args") = "", + D(source,make) + ) + + + + + + .def("get_num_channels",&source::get_num_channels, + D(source,get_num_channels) + ) + + + .def("seek",&source::seek, + py::arg("seek_point"), + py::arg("whence"), + py::arg("chan") = 0, + D(source,seek) + ) + + + .def("get_sample_rates",&source::get_sample_rates, + D(source,get_sample_rates) + ) + + + .def("set_sample_rate",&source::set_sample_rate, + py::arg("rate"), + D(source,set_sample_rate) + ) + + + .def("get_sample_rate",&source::get_sample_rate, + D(source,get_sample_rate) + ) + + + .def("get_freq_range",&source::get_freq_range, + py::arg("chan") = 0, + D(source,get_freq_range) + ) + + + .def("set_center_freq",&source::set_center_freq, + py::arg("freq"), + py::arg("chan") = 0, + D(source,set_center_freq) + ) + + + .def("get_center_freq",&source::get_center_freq, + py::arg("chan") = 0, + D(source,get_center_freq) + ) + + + .def("set_freq_corr",&source::set_freq_corr, + py::arg("ppm"), + py::arg("chan") = 0, + D(source,set_freq_corr) + ) + + + .def("get_freq_corr",&source::get_freq_corr, + py::arg("chan") = 0, + D(source,get_freq_corr) + ) + + + .def("get_gain_names",&source::get_gain_names, + py::arg("chan") = 0, + D(source,get_gain_names) + ) + + + .def("get_gain_range",(osmosdr::gain_range_t (source::*)(size_t))&source::get_gain_range, + py::arg("chan") = 0, + D(source,get_gain_range,0) + ) + + + .def("get_gain_range",(osmosdr::gain_range_t (source::*)(std::string const &, size_t))&source::get_gain_range, + py::arg("name"), + py::arg("chan") = 0, + D(source,get_gain_range,1) + ) + + + .def("set_gain_mode",&source::set_gain_mode, + py::arg("automatic"), + py::arg("chan") = 0, + D(source,set_gain_mode) + ) + + + .def("get_gain_mode",&source::get_gain_mode, + py::arg("chan") = 0, + D(source,get_gain_mode) + ) + + + .def("set_gain",(double (source::*)(double, size_t))&source::set_gain, + py::arg("gain"), + py::arg("chan") = 0, + D(source,set_gain,0) + ) + + + .def("set_gain",(double (source::*)(double, std::string const &, size_t))&source::set_gain, + py::arg("gain"), + py::arg("name"), + py::arg("chan") = 0, + D(source,set_gain,1) + ) + + + .def("get_gain",(double (source::*)(size_t))&source::get_gain, + py::arg("chan") = 0, + D(source,get_gain,0) + ) + + + .def("get_gain",(double (source::*)(std::string const &, size_t))&source::get_gain, + py::arg("name"), + py::arg("chan") = 0, + D(source,get_gain,1) + ) + + + .def("set_if_gain",&source::set_if_gain, + py::arg("gain"), + py::arg("chan") = 0, + D(source,set_if_gain) + ) + + + .def("set_bb_gain",&source::set_bb_gain, + py::arg("gain"), + py::arg("chan") = 0, + D(source,set_bb_gain) + ) + + + .def("get_antennas",&source::get_antennas, + py::arg("chan") = 0, + D(source,get_antennas) + ) + + + .def("set_antenna",&source::set_antenna, + py::arg("antenna"), + py::arg("chan") = 0, + D(source,set_antenna) + ) + + + .def("get_antenna",&source::get_antenna, + py::arg("chan") = 0, + D(source,get_antenna) + ) + + + .def("set_dc_offset_mode",&source::set_dc_offset_mode, + py::arg("mode"), + py::arg("chan") = 0, + D(source,set_dc_offset_mode) + ) + + + .def("set_dc_offset",&source::set_dc_offset, + py::arg("offset"), + py::arg("chan") = 0, + D(source,set_dc_offset) + ) + + + .def("set_iq_balance_mode",&source::set_iq_balance_mode, + py::arg("mode"), + py::arg("chan") = 0, + D(source,set_iq_balance_mode) + ) + + + .def("set_iq_balance",&source::set_iq_balance, + py::arg("balance"), + py::arg("chan") = 0, + D(source,set_iq_balance) + ) + + + .def("set_bandwidth",&source::set_bandwidth, + py::arg("bandwidth"), + py::arg("chan") = 0, + D(source,set_bandwidth) + ) + + + .def("get_bandwidth",&source::get_bandwidth, + py::arg("chan") = 0, + D(source,get_bandwidth) + ) + + + .def("get_bandwidth_range",&source::get_bandwidth_range, + py::arg("chan") = 0, + D(source,get_bandwidth_range) + ) + + + .def("set_time_source",&source::set_time_source, + py::arg("source"), + py::arg("mboard") = 0, + D(source,set_time_source) + ) + + + .def("get_time_source",&source::get_time_source, + py::arg("mboard"), + D(source,get_time_source) + ) + + + .def("get_time_sources",&source::get_time_sources, + py::arg("mboard"), + D(source,get_time_sources) + ) + + + .def("set_clock_source",&source::set_clock_source, + py::arg("source"), + py::arg("mboard") = 0, + D(source,set_clock_source) + ) + + + .def("get_clock_source",&source::get_clock_source, + py::arg("mboard"), + D(source,get_clock_source) + ) + + + .def("get_clock_sources",&source::get_clock_sources, + py::arg("mboard"), + D(source,get_clock_sources) + ) + + + .def("get_clock_rate",&source::get_clock_rate, + py::arg("mboard") = 0, + D(source,get_clock_rate) + ) + + + .def("set_clock_rate",&source::set_clock_rate, + py::arg("rate"), + py::arg("mboard") = 0, + D(source,set_clock_rate) + ) + + + .def("get_time_now",&source::get_time_now, + py::arg("mboard") = 0, + D(source,get_time_now) + ) + + + .def("get_time_last_pps",&source::get_time_last_pps, + py::arg("mboard") = 0, + D(source,get_time_last_pps) + ) + + + .def("set_time_now",&source::set_time_now, + py::arg("time_spec"), + py::arg("mboard") = 0, + D(source,set_time_now) + ) + + + .def("set_time_next_pps",&source::set_time_next_pps, + py::arg("time_spec"), + D(source,set_time_next_pps) + ) + + + .def("set_time_unknown_pps",&source::set_time_unknown_pps, + py::arg("time_spec"), + D(source,set_time_unknown_pps) + ) + + ; + + + + +} + + + + + + + diff --git a/swig/CMakeLists.txt b/swig/CMakeLists.txt deleted file mode 100644 index 159f212..0000000 --- a/swig/CMakeLists.txt +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2011 Free Software Foundation, Inc. -# -# This file is part of gr-osmosdr -# -# gr-osmosdr 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. -# -# gr-osmosdr 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 gr-osmosdr; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. - -######################################################################## -# Include swig generation macros -######################################################################## -find_package(SWIG) -find_package(PythonLibs 3) -if(NOT SWIG_FOUND OR NOT PYTHONLIBS_FOUND) - return() -endif() -include(GrSwig) -include(GrPython) - -######################################################################## -# Setup swig generation -######################################################################## -set(GR_SWIG_INCLUDE_DIRS $) -set(GR_SWIG_TARGET_DEPS gnuradio::runtime_swig) - -set(GR_SWIG_LIBRARIES gnuradio-osmosdr) - -set(GR_SWIG_DOC_FILE ${CMAKE_CURRENT_BINARY_DIR}/osmosdr_swig_doc.i) -set(GR_SWIG_DOC_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../include/osmosdr) - -GR_SWIG_MAKE(osmosdr_swig osmosdr_swig.i) - -######################################################################## -# Install the build swig module -######################################################################## -GR_SWIG_INSTALL(TARGETS osmosdr_swig DESTINATION ${GR_PYTHON_DIR}/osmosdr) - -######################################################################## -# Install swig .i files for development -######################################################################## -install( - FILES - osmosdr_swig.i - ${CMAKE_CURRENT_BINARY_DIR}/osmosdr_swig_doc.i - DESTINATION ${GR_INCLUDE_DIR}/osmosdr/swig -) diff --git a/swig/osmosdr_swig.i b/swig/osmosdr_swig.i deleted file mode 100644 index da42e6e..0000000 --- a/swig/osmosdr_swig.i +++ /dev/null @@ -1,82 +0,0 @@ -/* -*- c++ -*- */ - -#define OSMOSDR_API - -// suppress Warning 319: No access specifier given for base class 'boost::noncopyable' (ignored). -#pragma SWIG nowarn=319 - -%include "gnuradio.i" // the common stuff - -//load generated python docstrings -%include "osmosdr_swig_doc.i" - -%{ -#include "osmosdr/device.h" -#include "osmosdr/source.h" -#include "osmosdr/sink.h" -%} - -// Workaround for a SWIG 2.0.4 bug with templates. Probably needs to be looked in to. -%{ -#if PY_VERSION_HEX >= 0x03020000 -# define SWIGPY_SLICE_ARG(obj) ((PyObject*) (obj)) -#else -# define SWIGPY_SLICE_ARG(obj) ((PySliceObject*) (obj)) -#endif -%} - -%template(string_vector_t) std::vector; - -//%template(size_vector_t) std::vector; - -%include - -%ignore osmosdr::device_t::operator[]; //ignore warnings about %extend - -%template(string_string_dict_t) std::map; //define before device -%template(devices_t) std::vector; -%include - -//%extend std::map{ -// std::string __getitem__(std::string key) {return (*self)[key];} -// void __setitem__(std::string key, std::string val) {(*self)[key] = val;} -//}; - -%template(range_vector_t) std::vector; //define before range -%include - -%include - -%extend osmosdr::time_spec_t{ - osmosdr::time_spec_t __add__(const osmosdr::time_spec_t &what) - { - osmosdr::time_spec_t temp = *self; - temp += what; - return temp; - } - osmosdr::time_spec_t __sub__(const osmosdr::time_spec_t &what) - { - osmosdr::time_spec_t temp = *self; - temp -= what; - return temp; - } -}; - -%define OSMOSDR_SWIG_BLOCK_MAGIC2(PKG, BASE_NAME) -%template(BASE_NAME ## _sptr) std::shared_ptr; -%pythoncode %{ -BASE_NAME ## _sptr.__repr__ = lambda self: "" % (self.name(), self.unique_id()) -BASE_NAME = BASE_NAME.make; -%} -%enddef - -%include "osmosdr/source.h" -%include "osmosdr/sink.h" - -OSMOSDR_SWIG_BLOCK_MAGIC2(osmosdr,source); -OSMOSDR_SWIG_BLOCK_MAGIC2(osmosdr,sink); - -%{ -static const size_t ALL_MBOARDS = osmosdr::ALL_MBOARDS; -%} -//static const size_t ALL_MBOARDS; -- cgit v1.2.3