def test(case, builder): _logger.info("%s: Testing builder %s", case, builder) commands = [] def _subprocessMocker(self, cmd_with_args, shell=False, env=None): commands.append(cmd_with_args) return [] with mock.patch('hdlcc.builders.%s.checkEnvironment' % builder, lambda self: setattr(self, '_version', '<foo>')): with mock.patch('hdlcc.builders.%s._subprocessRunner' % builder, _subprocessMocker): parser = ConfigParser(p.join(TEST_CONFIG_PARSER_SUPPORT_PATH, 'project_wo_builder_wo_target_dir.prj')) for cmd in commands: _logger.warning('>' + str(cmd)) it.assertEquals(parser.getBuilder(), builder.lower())
def test(): parser = ConfigParser(p.join(TEST_CONFIG_PARSER_SUPPORT_PATH, 'project_wo_builder_wo_target_dir.prj')) it.assertEquals(parser.getBuilder(), 'fallback')
class HdlCodeCheckerBase(object): """ HDL Code Checker project builder class """ _USE_THREADS = True _MAX_REBUILD_ATTEMPTS = 20 __metaclass__ = abc.ABCMeta def __init__(self, project_file=None): self._start_dir = p.abspath(os.curdir) self._logger = logging.getLogger(__name__) self._build_sequence_cache = {} self.project_file = project_file self._config = None self.builder = None self._setupEnvIfNeeded() self._saveCache() def _getCacheFilename(self, target_dir=None): """ Returns the cache file name for a given project file """ if target_dir is None: if self._config is None or self._config.getBuilder() == 'fallback': return None else: target_dir = self._config.getTargetDir() return p.join(target_dir, '.hdlcc.cache') def _saveCache(self): """ Dumps project object to a file to recover its state later """ cache_fname = self._getCacheFilename() if self.builder.builder_name == 'fallback' or cache_fname is None: self._logger.debug("Skipping cache save") return state = {'serializer' : serializer.__name__, '_logger': {'name' : self._logger.name, 'level' : self._logger.level}, 'builder' : self.builder.getState(), '_config' : self._config.getState()} self._logger.debug("Saving state to '%s'", cache_fname) if not p.exists(p.dirname(cache_fname)): os.mkdir(p.dirname(cache_fname)) dump(state, open(cache_fname, 'w')) def _recoverCache(self, target_dir): """ Tries to recover cached info for the given project_file. If something goes wrong, assume the cache is invalid and return nothing. Otherwise, return the cached object """ cache_fname = self._getCacheFilename(target_dir) # if self.project_file is None or cache_fname is None: if cache_fname is None: self._logger.warning("Can't recover cache from None") return _logger.debug("Trying to recover from '%s'", cache_fname) cache = None if not p.exists(cache_fname): # pragma: no cover _logger.debug("File not found") return try: cache = serializer.load(open(cache_fname, 'r')) self._handleUiInfo("Recovered cache from '%s' (used '%s')" % (cache_fname, serializer.__package__)) self._setState(cache) self.builder.checkEnvironment() except ValueError: self._handleUiError( "Unable to recover cache from '%s' using '%s'\n" "Traceback:\n%s" % \ (cache_fname, serializer.__package__, traceback.format_exc())) def _setupEnvIfNeeded(self): """ Updates or creates the environment, which includes checking if the configuration file should be parsed and creating the appropriate builder objects """ try: # If the configuration is undefined, try to extract the # target dir from the project file so we can have a hint of # where the cache file should be if self._config is None and self.project_file is not None: target_dir, _ = ConfigParser.simpleParse(self.project_file) self._recoverCache(target_dir) # No configuration defined means we failed to recover it # from the cache if self._config is None: self._config = ConfigParser(self.project_file) # If the builder is still undefined we failed to recover # from cache if self.builder is None: builder_name = self._config.getBuilder() builder_class = hdlcc.builders.getBuilderByName(builder_name) self.builder = builder_class(self._config.getTargetDir()) self._logger.info("Selected builder is '%s'", self.builder.builder_name) assert self.builder is not None except hdlcc.exceptions.SanityCheckError as exc: self._handleUiError("Failed to create builder '%s'" % exc.builder) self.builder = hdlcc.builders.Fallback(self._config.getTargetDir()) def clean(self): """ Clean up generated files """ cache_fname = self._getCacheFilename() if cache_fname is not None and p.exists(cache_fname): _logger.debug("Removing cached info in '%s'", cache_fname) os.remove(cache_fname) target_dir = self._config.getTargetDir() if p.exists(target_dir): _logger.debug("Removing target dir '%s'", target_dir) shutil.rmtree(target_dir) del self._config del self.builder self._config = None self.builder = None def _setState(self, state): """ Serializer load implementation """ self._logger = logging.getLogger(state['_logger']['name']) self._logger.setLevel(state['_logger']['level']) del state['_logger'] self._config = ConfigParser.recoverFromState(state['_config']) builder_name = self._config.getBuilder() self._logger.debug("Recovered builder is '%s'", builder_name) builder_class = hdlcc.builders.getBuilderByName(builder_name) self.builder = builder_class.recoverFromState(state['builder']) @abc.abstractmethod def _handleUiInfo(self, message): """ Method that should be overriden to handle info messages from HDL Code Checker to the user """ @abc.abstractmethod def _handleUiWarning(self, message): """ Method that should be overriden to handle warning messages from HDL Code Checker to the user """ @abc.abstractmethod def _handleUiError(self, message): """ Method that should be overriden to handle errors messages from HDL Code Checker to the user """ def _getSourceByPath(self, path): """ Get the source object, flags and any additional info to be displayed """ source = None remarks = [] try: source = self._config.getSourceByPath(path) except KeyError: pass # If the source file was not found on the configuration file, add this # as a remark. # Also, create a source parser object with some library so the user can # at least have some info on the source if source is None: if self.builder.builder_name != 'fallback': remarks += [{ 'checker' : 'hdlcc', 'line_number' : '', 'column' : '', 'filename' : '', 'error_number' : '', 'error_type' : 'W', 'error_message' : 'Path "%s" not found in project file' % p.abspath(path)}] self._logger.info("Path %s not found in the project file", p.abspath(path)) cls = VhdlParser if getFileType(path) == 'vhdl' else VerilogParser source = cls(path, library='undefined') return source, remarks def _resolveRelativeNames(self, source): """ Translate raw dependency list parsed from a given source to the project name space """ for dependency in source.getDependencies(): if dependency['library'] in self.builder.getBuiltinLibraries() or \ dependency['unit'] == 'all' or \ (dependency['library'] == source.library and \ dependency['unit'] in [x['name'] for x in source.getDesignUnits()]): continue yield dependency['library'], dependency['unit'] @staticmethod def _sortBuildMessages(records): """ Sorts a given set of build records """ return sorted(records, key=lambda x: \ (x['error_type'], str(x['line_number']), str(x['error_number']))) def getBuildSequence(self, source): """ Wrapper to _getBuildSequence passing the initial build sequence list empty and caching the result """ # Despite we renew the cache when on buffer enter, we must also # check if any file has been changed by some background process # that the editor is unaware of (Vivado maybe?) To cope with # this, we'll check if the newest modification time of the build # sequence hasn't changed since we cached the build sequence key = str(source.filename) if key not in self._build_sequence_cache: build_sequence = [] self._getBuildSequence(source, build_sequence) if build_sequence: timestamp = max([x.getmtime() for x in build_sequence]) else: timestamp = 0 self._build_sequence_cache[key] = { 'sequence': build_sequence, 'timestamp' : timestamp} else: cached_sequence = self._build_sequence_cache[key]['sequence'] cached_timestamp = self._build_sequence_cache[key]['timestamp'] if cached_sequence: current_timestamp = max([x.getmtime() for x in cached_sequence]) else: current_timestamp = 0 if current_timestamp > cached_timestamp: build_sequence = [] self._build_sequence_cache[key] = { 'sequence': build_sequence, 'timestamp' : current_timestamp} return self._build_sequence_cache[key]['sequence'] def _getBuildSequence(self, source, build_sequence, reference=None): """ Recursively finds out the dependencies of the given source file """ self._logger.debug("Checking build sequence for %s", source) for library, unit in self._resolveRelativeNames(source): # Get a list of source files that contains this design unit dependencies_list = self._config.discoverSourceDependencies( unit, library) if not dependencies_list: continue dependency = dependencies_list[0] # If we found more than a single file, then multiple files # have the same entity or package name and we failed to # identify the real file if len(dependencies_list) != 1: self._handleUiWarning( "Returning dependency '%s' for %s.%s in file '%s', but " "there were %d other matches: %s. The selected option may " "be sub-optimal" % ( dependency.filename, library, unit, source.filename, len(dependencies_list), ', '.join([x.filename for x in dependencies_list]))) # Check if we found out that a dependency is the same we # found in the previous call to break the circular loop if dependency == reference: return removeDuplicates(build_sequence) if dependency not in build_sequence: self._getBuildSequence(dependency, reference=source, build_sequence=build_sequence) if dependency not in build_sequence: build_sequence.append(dependency) build_sequence = removeDuplicates(build_sequence) def _getBuilderMessages(self, source, batch_mode=False): """ Builds the given source taking care of recursively building its dependencies first """ try: flags = self._config.getBuildFlags(source.filename, batch_mode) except KeyError: flags = [] self._logger.info("Building '%s', batch_mode = %s", str(source.filename), batch_mode) build_sequence = self.getBuildSequence(source) self._logger.debug("Compilation build_sequence is:\n%s", "\n".join([x.filename for x in build_sequence])) for _source in build_sequence: _flags = self._config.getBuildFlags(_source.filename, batch_mode=False) _ = self._buildAndHandleRebuilds(_source, forced=False, flags=_flags) source_records = self._buildAndHandleRebuilds(source, forced=True, flags=flags) return self._sortBuildMessages(source_records) def _buildAndHandleRebuilds(self, source, *args, **kwargs): """ Builds the given source and handle any files that might require rebuilding until there is nothing to rebuild. The number of iteractions is fixed in 10. """ # Limit the amount of calls to rebuild the same file to avoid # hanging the server for _ in range(self._MAX_REBUILD_ATTEMPTS): records, rebuilds = self.builder.build(source, *args, **kwargs) if rebuilds: self._handleRebuilds(rebuilds, source) else: return records self._handleUiError("Unable to build '%s' after %d attempts" % (source, self._MAX_REBUILD_ATTEMPTS)) def _handleRebuilds(self, rebuilds, source): """ Resolves hints found in the rebuild list into source objects and rebuild them """ self._logger.info("Building '%s' triggers rebuilding: %s", source, ", ".join([str(x) for x in rebuilds])) for rebuild in rebuilds: self._logger.debug("Rebuild hint: '%s'", rebuild) if 'rebuild_path' in rebuild: rebuild_sources = [self._getSourceByPath(rebuild['rebuild_path'])[0]] else: unit_name = rebuild.get('unit_name', None) library_name = rebuild.get('library_name', None) unit_type = rebuild.get('unit_type', None) if library_name is not None: rebuild_sources = self._config.findSourcesByDesignUnit( unit_name, library_name) elif unit_type is not None: library = source.getMatchingLibrary( unit_type, unit_name) rebuild_sources = self._config.findSourcesByDesignUnit( unit_name, library) else: # pragma: no cover assert False, ', '.join([x.filename for x in rebuild_sources]) for rebuild_source in rebuild_sources: self._getBuilderMessages(rebuild_source, batch_mode=True) def _isBuilderCallable(self): """ Checks if all preconditions for calling the builder have been met """ if self._config.filename is None: return False return True def getMessagesByPath(self, path, *args, **kwargs): """ Returns the messages for the given path, including messages from the configured builder (if available) and static checks """ self._setupEnvIfNeeded() source, remarks = self._getSourceByPath(path) return self._sortBuildMessages( self.getMessagesBySource(source, *args, **kwargs) + remarks) def getMessagesBySource(self, source, batch_mode=False): """ Returns the messages for the given source, including messages from the configured builder (if available) and static checks Extra arguments are """ self._setupEnvIfNeeded() if self._USE_THREADS: records = [] pool = ThreadPool() static_check = pool.apply_async( getStaticMessages, args=(source.getSourceContent().split('\n'), )) if self._isBuilderCallable(): builder_check = pool.apply_async(self._getBuilderMessages, args=[source, batch_mode]) records += builder_check.get() records += static_check.get() pool.terminate() pool.join() else: records = getStaticMessages(source.getSourceContent().split('\n')) if self._isBuilderCallable(): records += self._getBuilderMessages(source, batch_mode) self._saveCache() return records def getMessagesWithText(self, path, content): """ Gets messages from a given path with a different content, for the cases when the buffer content has been modified """ self._logger.debug("Getting messages for '%s' with content", path) self._setupEnvIfNeeded() source, remarks = self._getSourceByPath(path) source.setBufferContent(content) messages = self.getMessagesBySource(source) source.clearBufferContent() return messages + remarks def getSources(self): """ Returns a list of VhdlSourceFile objects parsed """ self._setupEnvIfNeeded() return self._config.getSources() def onBufferVisit(self, path): """ Runs tasks whenever a buffer is being visited. Currently this means caching the build sequence before the file is actually checked, so the overall wait time is reduced """ self._setupEnvIfNeeded() source, _ = self._getSourceByPath(path) _ = self.getBuildSequence(source) def onBufferLeave(self, _): """ Runs actions when leaving a buffer. """ pass
def test(): parser = ConfigParser( p.join(TEST_CONFIG_PARSER_SUPPORT_PATH, 'project_wo_builder_wo_target_dir.prj')) it.assertEquals(parser.getBuilder(), 'fallback')
lambda self: setattr(self, '_version', '<foo>')), mock.patch('hdlcc.builders.%s._subprocessRunner' % builder, _subprocessMocker), mock.patch('hdlcc.builders.%s.isAvailable' % builder, isAvailable) ] for patch in patches: patch.start() try: parser = ConfigParser( p.join(TEST_CONFIG_PARSER_SUPPORT_PATH, 'project_wo_builder_wo_target_dir.prj')) for cmd in commands: _logger.warning('>' + str(cmd)) it.assertEquals(parser.getBuilder(), builder.lower()) finally: for patch in patches: patch.stop() @it.should("use fallback if no builder pass") def test(): parser = ConfigParser( p.join(TEST_CONFIG_PARSER_SUPPORT_PATH, 'project_wo_builder_wo_target_dir.prj')) it.assertEquals(parser.getBuilder(), 'fallback') it.createTests(globals())
class HdlCodeCheckerBase(object): """ HDL Code Checker project builder class """ _USE_THREADS = True _MAX_REBUILD_ATTEMPTS = 20 __metaclass__ = abc.ABCMeta def __init__(self, project_file=None): self._start_dir = p.abspath(os.curdir) self._logger = logging.getLogger(__name__) self._build_sequence_cache = {} self.project_file = project_file self._config = None self.builder = None self._setupEnvIfNeeded() self._saveCache() def _getCacheFilename(self, target_dir=None): """ Returns the cache file name for a given project file """ if target_dir is None: if self._config is None or self._config.getBuilder() == 'fallback': return None else: target_dir = self._config.getTargetDir() return p.join(target_dir, '.hdlcc.cache') def _saveCache(self): """ Dumps project object to a file to recover its state later """ cache_fname = self._getCacheFilename() if self.builder.builder_name == 'fallback' or cache_fname is None: self._logger.debug("Skipping cache save") return state = { 'serializer': serializer.__name__, '_logger': { 'name': self._logger.name, 'level': self._logger.level }, 'builder': self.builder.getState(), '_config': self._config.getState() } self._logger.debug("Saving state to '%s'", cache_fname) if not p.exists(p.dirname(cache_fname)): os.mkdir(p.dirname(cache_fname)) dump(state, open(cache_fname, 'w')) def _recoverCache(self, target_dir): """ Tries to recover cached info for the given project_file. If something goes wrong, assume the cache is invalid and return nothing. Otherwise, return the cached object """ cache_fname = self._getCacheFilename(target_dir) # if self.project_file is None or cache_fname is None: if cache_fname is None: self._logger.warning("Can't recover cache from None") return _logger.debug("Trying to recover from '%s'", cache_fname) cache = None if not p.exists(cache_fname): # pragma: no cover _logger.debug("File not found") return try: cache = serializer.load(open(cache_fname, 'r')) self._handleUiInfo("Recovered cache from '%s' (used '%s')" % (cache_fname, serializer.__package__)) self._setState(cache) self.builder.checkEnvironment() except ValueError: self._handleUiError( "Unable to recover cache from '%s' using '%s'\n" "Traceback:\n%s" % \ (cache_fname, serializer.__package__, traceback.format_exc())) def _setupEnvIfNeeded(self): """ Updates or creates the environment, which includes checking if the configuration file should be parsed and creating the appropriate builder objects """ try: # If the configuration is undefined, try to extract the # target dir from the project file so we can have a hint of # where the cache file should be if self._config is None and self.project_file is not None: target_dir, _ = ConfigParser.simpleParse(self.project_file) self._recoverCache(target_dir) # No configuration defined means we failed to recover it # from the cache if self._config is None: self._config = ConfigParser(self.project_file) # If the builder is still undefined we failed to recover # from cache if self.builder is None: builder_name = self._config.getBuilder() builder_class = hdlcc.builders.getBuilderByName(builder_name) self.builder = builder_class(self._config.getTargetDir()) self._logger.info("Selected builder is '%s'", self.builder.builder_name) assert self.builder is not None except hdlcc.exceptions.SanityCheckError as exc: self._handleUiError("Failed to create builder '%s'" % exc.builder) self.builder = hdlcc.builders.Fallback(self._config.getTargetDir()) def clean(self): """ Clean up generated files """ cache_fname = self._getCacheFilename() if cache_fname is not None and p.exists(cache_fname): _logger.debug("Removing cached info in '%s'", cache_fname) os.remove(cache_fname) target_dir = self._config.getTargetDir() if p.exists(target_dir): _logger.debug("Removing target dir '%s'", target_dir) shutil.rmtree(target_dir) del self._config del self.builder self._config = None self.builder = None def _setState(self, state): """ Serializer load implementation """ self._logger = logging.getLogger(state['_logger']['name']) self._logger.setLevel(state['_logger']['level']) del state['_logger'] self._config = ConfigParser.recoverFromState(state['_config']) builder_name = self._config.getBuilder() self._logger.debug("Recovered builder is '%s'", builder_name) builder_class = hdlcc.builders.getBuilderByName(builder_name) self.builder = builder_class.recoverFromState(state['builder']) @abc.abstractmethod def _handleUiInfo(self, message): """ Method that should be overriden to handle info messages from HDL Code Checker to the user """ @abc.abstractmethod def _handleUiWarning(self, message): """ Method that should be overriden to handle warning messages from HDL Code Checker to the user """ @abc.abstractmethod def _handleUiError(self, message): """ Method that should be overriden to handle errors messages from HDL Code Checker to the user """ def getSourceByPath(self, path): """ Get the source object, flags and any additional info to be displayed """ source = None remarks = [] try: source = self._config.getSourceByPath(path) except KeyError: pass # If the source file was not found on the configuration file, add this # as a remark. # Also, create a source parser object with some library so the user can # at least have some info on the source if source is None: if self.builder.builder_name != 'fallback': remarks += [{ 'checker': 'hdlcc', 'line_number': '', 'column': '', 'filename': '', 'error_number': '', 'error_type': 'W', 'error_message': 'Path "%s" not found in project file' % p.abspath(path) }] self._logger.info("Path %s not found in the project file", p.abspath(path)) cls = VhdlParser if getFileType(path) == 'vhdl' else VerilogParser source = cls(path, library='undefined') return source, remarks def _resolveRelativeNames(self, source): """ Translate raw dependency list parsed from a given source to the project name space """ for dependency in source.getDependencies(): if dependency['library'] in self.builder.getBuiltinLibraries() or \ dependency['unit'] == 'all' or \ (dependency['library'] == source.library and \ dependency['unit'] in [x['name'] for x in source.getDesignUnits()]): continue yield dependency['library'], dependency['unit'] @staticmethod def _sortBuildMessages(records): """ Sorts a given set of build records """ return sorted(records, key=lambda x: \ (x['error_type'], str(x['line_number']), str(x['error_number']))) def updateBuildSequenceCache(self, source): """ Wrapper to _getBuildSequence passing the initial build sequence list empty and caching the result """ # Despite we renew the cache when on buffer enter, we must also # check if any file has been changed by some background process # that the editor is unaware of (Vivado maybe?) To cope with # this, we'll check if the newest modification time of the build # sequence hasn't changed since we cached the build sequence key = str(source.filename) if key not in self._build_sequence_cache: build_sequence = [] self._getBuildSequence(source, build_sequence) if build_sequence: timestamp = max([x.getmtime() for x in build_sequence]) else: timestamp = 0 self._build_sequence_cache[key] = { 'sequence': build_sequence, 'timestamp': timestamp } else: cached_sequence = self._build_sequence_cache[key]['sequence'] cached_timestamp = self._build_sequence_cache[key]['timestamp'] if cached_sequence: current_timestamp = max( [x.getmtime() for x in cached_sequence] + [source.getmtime()]) else: current_timestamp = 0 if current_timestamp > cached_timestamp: self._logger.debug("Timestamp change, rescanning build " "sequence") build_sequence = [] self._getBuildSequence(source, build_sequence) self._build_sequence_cache[key] = { 'sequence': build_sequence, 'timestamp': current_timestamp } return self._build_sequence_cache[key]['sequence'] def _getBuildSequence(self, source, build_sequence, reference=None): """ Recursively finds out the dependencies of the given source file """ self._logger.debug("Checking build sequence for %s", source) for library, unit in self._resolveRelativeNames(source): # Get a list of source files that contains this design unit dependencies_list = self._config.discoverSourceDependencies( unit, library) if not dependencies_list: continue dependency = dependencies_list[0] # If we found more than a single file, then multiple files # have the same entity or package name and we failed to # identify the real file if len(dependencies_list) != 1: self._handleUiWarning( "Returning dependency '%s' for %s.%s in file '%s', but " "there were %d other matches: %s. The selected option may " "be sub-optimal" % (dependency.filename, library, unit, source.filename, len(dependencies_list), ', '.join( [x.filename for x in dependencies_list]))) # Check if we found out that a dependency is the same we # found in the previous call to break the circular loop if dependency == reference: return removeDuplicates(build_sequence) if dependency not in build_sequence: self._getBuildSequence(dependency, reference=source, build_sequence=build_sequence) if dependency not in build_sequence: build_sequence.append(dependency) build_sequence = removeDuplicates(build_sequence) def _getBuilderMessages(self, source, batch_mode=False): """ Builds the given source taking care of recursively building its dependencies first """ try: flags = self._config.getBuildFlags(source.filename, batch_mode) except KeyError: flags = [] self._logger.info("Building '%s', batch_mode = %s", str(source.filename), batch_mode) build_sequence = self.updateBuildSequenceCache(source) self._logger.debug("Compilation build_sequence is:\n%s", "\n".join([x.filename for x in build_sequence])) for _source in build_sequence: _flags = self._config.getBuildFlags(_source.filename, batch_mode=False) _ = self._buildAndHandleRebuilds(_source, forced=False, flags=_flags) source_records = self._buildAndHandleRebuilds(source, forced=True, flags=flags) return self._sortBuildMessages(source_records) def _buildAndHandleRebuilds(self, source, *args, **kwargs): """ Builds the given source and handle any files that might require rebuilding until there is nothing to rebuild. The number of iteractions is fixed in 10. """ # Limit the amount of calls to rebuild the same file to avoid # hanging the server for _ in range(self._MAX_REBUILD_ATTEMPTS): records, rebuilds = self.builder.build(source, *args, **kwargs) if rebuilds: self._handleRebuilds(rebuilds, source) else: return records self._handleUiError("Unable to build '%s' after %d attempts" % (source, self._MAX_REBUILD_ATTEMPTS)) def _handleRebuilds(self, rebuilds, source): """ Resolves hints found in the rebuild list into source objects and rebuild them """ self._logger.info("Building '%s' triggers rebuilding: %s", source, ", ".join([str(x) for x in rebuilds])) for rebuild in rebuilds: self._logger.debug("Rebuild hint: '%s'", rebuild) if 'rebuild_path' in rebuild: rebuild_sources = [ self.getSourceByPath(rebuild['rebuild_path'])[0] ] else: unit_name = rebuild.get('unit_name', None) library_name = rebuild.get('library_name', None) unit_type = rebuild.get('unit_type', None) if library_name is not None: rebuild_sources = self._config.findSourcesByDesignUnit( unit_name, library_name) elif unit_type is not None: library = source.getMatchingLibrary(unit_type, unit_name) rebuild_sources = self._config.findSourcesByDesignUnit( unit_name, library) else: # pragma: no cover assert False, ', '.join( [x.filename for x in rebuild_sources]) for rebuild_source in rebuild_sources: self._getBuilderMessages(rebuild_source, batch_mode=True) def _isBuilderCallable(self): """ Checks if all preconditions for calling the builder have been met """ if self._config.filename is None: return False return True def getMessagesByPath(self, path, *args, **kwargs): """ Returns the messages for the given path, including messages from the configured builder (if available) and static checks """ self._setupEnvIfNeeded() source, remarks = self.getSourceByPath(path) return self._sortBuildMessages( self.getMessagesBySource(source, *args, **kwargs) + remarks) def getMessagesBySource(self, source, batch_mode=False): """ Returns the messages for the given source, including messages from the configured builder (if available) and static checks Extra arguments are """ self._setupEnvIfNeeded() if self._USE_THREADS: records = [] pool = ThreadPool() static_check = pool.apply_async( getStaticMessages, args=(source.getRawSourceContent().split('\n'), )) if self._isBuilderCallable(): builder_check = pool.apply_async(self._getBuilderMessages, args=[source, batch_mode]) records += builder_check.get() records += static_check.get() pool.terminate() pool.join() else: records = getStaticMessages( source.getRawSourceContent().split('\n')) if self._isBuilderCallable(): records += self._getBuilderMessages(source, batch_mode) self._saveCache() return records def getMessagesWithText(self, path, content): """ Gets messages from a given path with a different content, for the cases when the buffer content has been modified """ self._logger.debug("Getting messages for '%s' with content", path) self._setupEnvIfNeeded() source, remarks = self.getSourceByPath(path) with source.havingBufferContent(content): messages = self.getMessagesBySource(source) # Some messages may not include the filename field when checking a # file by content. In this case, we'll assume the empty filenames # refer to the same filename we got in the first place for message in messages: if message['filename'] is None: message['filename'] = path return messages + remarks def getSources(self): """ Returns a list of VhdlSourceFile objects parsed """ self._setupEnvIfNeeded() return self._config.getSources() def onBufferVisit(self, path): """ Runs tasks whenever a buffer is being visited. Currently this means caching the build sequence before the file is actually checked, so the overall wait time is reduced """ self._setupEnvIfNeeded() source, _ = self.getSourceByPath(path) self.updateBuildSequenceCache(source) def onBufferLeave(self, _): """ Runs actions when leaving a buffer. """ pass