""" Copyright 2008-2015 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GNU Radio Companion is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ import os import sys from gnuradio import gr from . import ParseXML, extract_docs from .Constants import ( BLOCK_TREE_DTD, FLOW_GRAPH_DTD, DOMAIN_DTD, HIER_BLOCKS_LIB_DIR, BLOCK_DTD, DEFAULT_FLOW_GRAPH, BLOCKS_DIRS, PREFS_FILE, CORE_TYPES, PREFS_FILE_OLD, ) from .Element import Element from .odict import odict from ..gui import Messages from .generator import Generator class Platform(Element): is_platform = True def __init__(self): """ Make a platform for gnuradio. Args: name: the platform name version: the version string key: the unique platform key block_paths: the file paths to blocks in this platform block_dtd: the dtd validator for xml block wrappers default_flow_graph: the default flow graph file path generator: the generator class for this platform colors: a list of title, color_spec tuples license: a multi-line license (first line is copyright) website: the website url for this platform """ # Ensure hier and conf directories if not os.path.exists(HIER_BLOCKS_LIB_DIR): os.mkdir(HIER_BLOCKS_LIB_DIR) if not os.path.exists(os.path.dirname(PREFS_FILE)): os.mkdir(os.path.dirname(PREFS_FILE)) self.block_docstrings = {} self.block_docstrings_loaded_callback = lambda: None # dummy to be replaced by BlockTreeWindow self._docstring_extractor = extract_docs.SubprocessLoader( callback_query_result=self._save_docstring_extraction_result, callback_finished=lambda: self.block_docstrings_loaded_callback() ) Element.__init__(self) self._name = 'GNU Radio Companion' # Save the verion string to the first version = (gr.version(), gr.major_version(), gr.api_version(), gr.minor_version()) self._version = version[0] self._version_major = version[1] self._version_api = version[2] self._version_minor = version[3] self._version_short = version[1] + "." + version[2] + "." + version[3] self._key = 'grc' self._license = __doc__.strip() self._website = 'http://gnuradio.org' self._block_paths = list(set(BLOCKS_DIRS)) self._block_dtd = BLOCK_DTD self._default_flow_graph = DEFAULT_FLOW_GRAPH self._generator = Generator self._colors = [(name, color) for name, key, sizeof, color in CORE_TYPES] # Create a dummy flow graph for the blocks self._flow_graph = Element(self) self._blocks = None self._blocks_n = None self._category_trees_n = None self._domains = dict() self._connection_templates = dict() self.load_blocks() self._auto_hier_block_generate_chain = set() def _save_docstring_extraction_result(self, key, docstrings): docs = {} for match, docstring in docstrings.iteritems(): if not docstring or match.endswith('_sptr'): continue docstring = docstring.replace('\n\n', '\n').strip() docs[match] = docstring self.block_docstrings[key] = docs @staticmethod def _move_old_pref_file(): if PREFS_FILE == PREFS_FILE_OLD: return # prefs file overridden with env var if os.path.exists(PREFS_FILE_OLD) and not os.path.exists(PREFS_FILE): try: import shutil shutil.move(PREFS_FILE_OLD, PREFS_FILE) except Exception as e: print >> sys.stderr, e def load_blocks(self): self._docstring_extractor.start() self._load_blocks() self._docstring_extractor.finish() # self._docstring_extractor.wait() def load_block_xml(self, xml_file): block = self._load_block_xml(self, xml_file) self._docstring_extractor.query( block.get_key(), block.get_imports(raw=True), block.get_make(raw=True) ) return block @staticmethod def find_file_in_paths(filename, paths, cwd): """Checks the provided paths relative to cwd for a certain filename""" if not os.path.isdir(cwd): cwd = os.path.dirname(cwd) if isinstance(paths, str): paths = (p for p in paths.split(':') if p) for path in paths: path = os.path.expanduser(path) if not os.path.isabs(path): path = os.path.normpath(os.path.join(cwd, path)) file_path = os.path.join(path, filename) if os.path.exists(os.path.normpath(file_path)): return file_path def load_and_generate_flow_graph(self, file_path): """Loads a flowgraph from file and generates it""" Messages.set_indent(len(self._auto_hier_block_generate_chain)) Messages.send('>>> Loading: %r\n' % file_path) if file_path in self._auto_hier_block_generate_chain: Messages.send(' >>> Warning: cyclic hier_block dependency\n') return False self._auto_hier_block_generate_chain.add(file_path) try: flow_graph = self.get_new_flow_graph() flow_graph.grc_file_path = file_path # Other, nested higiter_blocks might be auto-loaded here flow_graph.import_data(self.parse_flow_graph(file_path)) flow_graph.rewrite() flow_graph.validate() if not flow_graph.is_valid(): raise Exception('Flowgraph invalid') except Exception as e: Messages.send('>>> Load Error: {}: {}\n'.format(file_path, str(e))) return False finally: self._auto_hier_block_generate_chain.discard(file_path) Messages.set_indent(len(self._auto_hier_block_generate_chain)) try: Messages.send('>>> Generating: {}\n'.format(file_path)) generator = self.get_generator()(flow_graph, file_path) generator.write() except Exception as e: Messages.send('>>> Generate Error: {}: {}\n'.format(file_path, str(e))) return False self.load_block_xml(generator.get_file_path_xml()) return True def _load_blocks(self): """load the blocks and block tree from the search paths""" # Reset self._blocks = odict() self._blocks_n = odict() self._category_trees_n = list() self._domains.clear() self._connection_templates.clear() ParseXML.xml_failures.clear() # Try to parse and load blocks for xml_file in self.iter_xml_files(): try: if xml_file.endswith("block_tree.xml"): self.load_category_tree_xml(xml_file) elif xml_file.endswith('domain.xml'): self.load_domain_xml(xml_file) else: self._load_block_xml(xml_file) except ParseXML.XMLSyntaxError as e: # print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file) pass except Exception as e: print >> sys.stderr, 'Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file) def iter_xml_files(self): """Iterator for block descriptions and category trees""" for block_path in map(lambda x: os.path.abspath(os.path.expanduser(x)), self._block_paths): if os.path.isfile(block_path): yield block_path elif os.path.isdir(block_path): for dirpath, dirnames, filenames in os.walk(block_path): for filename in sorted(filter(lambda f: f.endswith('.xml'), filenames)): yield os.path.join(dirpath, filename) def _load_block_xml(self, xml_file): """Load block description from xml file""" # Validate and import ParseXML.validate_dtd(xml_file, self._block_dtd) n = ParseXML.from_file(xml_file).find('block') n['block_wrapper_path'] = xml_file # inject block wrapper path # Get block instance and add it to the list of blocks block = self.Block(self._flow_graph, n) key = block.get_key() if key in self._blocks: print >> sys.stderr, 'Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file) else: # Store the block self._blocks[key] = block self._blocks_n[key] = n return block def load_category_tree_xml(self, xml_file): """Validate and parse category tree file and add it to list""" ParseXML.validate_dtd(xml_file, BLOCK_TREE_DTD) n = ParseXML.from_file(xml_file).find('cat') self._category_trees_n.append(n) def load_domain_xml(self, xml_file): """Load a domain properties and connection templates from XML""" ParseXML.validate_dtd(xml_file, DOMAIN_DTD) n = ParseXML.from_file(xml_file).find('domain') key = n.find('key') if not key: print >> sys.stderr, 'Warning: Domain with emtpy key.\n\tIgnoring: {}'.foramt(xml_file) return if key in self.get_domains(): # test against repeated keys print >> sys.stderr, 'Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file) return #to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '') def to_bool(s, d): if s is not None: return s.lower() not in ('false', 'off', '0', '') return d color = n.find('color') or '' try: import gtk # ugly but handy gtk.gdk.color_parse(color) except (ValueError, ImportError): if color: # no color is okay, default set in GUI print >> sys.stderr, 'Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key) color = None self._domains[key] = dict( name=n.find('name') or key, multiple_sinks=to_bool(n.find('multiple_sinks'), True), multiple_sources=to_bool(n.find('multiple_sources'), False), color=color ) for connection_n in n.findall('connection'): key = (connection_n.find('source_domain'), connection_n.find('sink_domain')) if not all(key): print >> sys.stderr, 'Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file) elif key in self._connection_templates: print >> sys.stderr, 'Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file) else: self._connection_templates[key] = connection_n.find('make') or '' def parse_flow_graph(self, flow_graph_file): """ Parse a saved flow graph file. Ensure that the file exists, and passes the dtd check. Args: flow_graph_file: the flow graph file Returns: nested data @throws exception if the validation fails """ flow_graph_file = flow_graph_file or self._default_flow_graph open(flow_graph_file, 'r') # Test open ParseXML.validate_dtd(flow_graph_file, FLOW_GRAPH_DTD) return ParseXML.from_file(flow_graph_file) def load_block_tree(self, block_tree): """ Load a block tree with categories and blocks. Step 1: Load all blocks from the xml specification. Step 2: Load blocks with builtin category specifications. Args: block_tree: the block tree object """ # Recursive function to load categories and blocks def load_category(cat_n, parent=None): # Add this category parent = (parent or []) + [cat_n.find('name')] block_tree.add_block(parent) # Recursive call to load sub categories map(lambda c: load_category(c, parent), cat_n.findall('cat')) # Add blocks in this category for block_key in cat_n.findall('block'): if block_key not in self.get_block_keys(): print >> sys.stderr, 'Warning: Block key "{}" not found when loading category tree.'.format(block_key) continue block = self.get_block(block_key) # If it exists, the block's category shall not be overridden by the xml tree if not block.get_category(): block.set_category(parent) # Recursively load the category trees and update the categories for each block for category_tree_n in self._category_trees_n: load_category(category_tree_n) # Add blocks to block tree for block in self.get_blocks(): # Blocks with empty categories are hidden if not block.get_category(): continue block_tree.add_block(block.get_category(), block) def __str__(self): return 'Platform - {}({})'.format(self.get_key(), self.get_name()) def get_new_flow_graph(self): return self.FlowGraph(platform=self) def get_generator(self): return self._generator ############################################## # Access Blocks ############################################## def get_block_keys(self): return self._blocks.keys() def get_block(self, key): return self._blocks[key] def get_blocks(self): return self._blocks.values() def get_new_block(self, flow_graph, key): return self.Block(flow_graph, n=self._blocks_n[key]) def get_domains(self): return self._domains def get_domain(self, key): return self._domains.get(key) def get_connection_templates(self): return self._connection_templates def get_name(self): return self._name def get_version(self): return self._version def get_version_major(self): return self._version_major def get_version_api(self): return self._version_api def get_version_minor(self): return self._version_minor def get_version_short(self): return self._version_short def get_key(self): return self._key def get_license(self): return self._license def get_website(self): return self._website def get_colors(self): return self._colors def get_block_paths(self): return self._block_paths