def run(self, artifacts: List[Artifact]) -> List[Artifact]: logger = logging.getLogger(__name__) if len(artifacts) == 1: artifact = artifacts[0] else: msg = ('C Compiler expects only one Artifact, ' f'but was given {len(artifacts)}') raise TaskException(msg) command = [self._compiler] command.extend(self._flags) command.append(str(artifact.location)) output_file = (self._workspace / artifact.location.with_suffix('.o').name) command.extend(['-o', str(output_file)]) logger.debug('Running command: ' + ' '.join(command)) subprocess.run(command, check=True) object_artifact = Artifact(output_file, BinaryObject, Compiled) for definition in artifact.defines: object_artifact.add_definition(definition) return [object_artifact]
def run(self, artifacts: List[Artifact]) -> List[Artifact]: if len(artifacts) == 1: artifact = artifacts[0] else: msg = ('C Analyser expects only one Artifact, ' f'but was given {len(artifacts)}') raise TaskException(msg) reader = FileTextReader(artifact.location) state = CWorkingState(self.database) state.remove_c_file(reader.filename) new_artifact = Artifact(artifact.location, artifact.filetype, Analysed) state = CWorkingState(self.database) state.remove_c_file(reader.filename) index = clang.cindex.Index.create() translation_unit = index.parse(reader.filename, args=["-xc"]) # Create include region line mappings self._locate_include_regions(translation_unit) # Now walk the actual nodes and find all relevant external symbols usr_includes = [] current_def = None for node in translation_unit.cursor.walk_preorder(): if node.kind == clang.cindex.CursorKind.FUNCTION_DECL: if (node.is_definition() and node.linkage == clang.cindex.LinkageKind.EXTERNAL): # This should catch function definitions which are exposed # to the rest of the application current_def = CSymbolID(node.spelling, artifact.location) state.add_c_symbol(current_def) new_artifact.add_definition(node.spelling) else: # Any other declarations should be coming in via headers, # we can use the injected pragmas to work out whether these # are coming from system headers or user headers if (self._check_for_include( node.location.line) == "usr_include"): usr_includes.append(node.spelling) elif (node.kind == clang.cindex.CursorKind.CALL_EXPR): # When encountering a function call we should be able to # cross-reference it with a definition seen earlier; and # if it came from a user supplied header then we will # consider it a dependency within the project if node.spelling in usr_includes and current_def is not None: # TODO: Assumption that the most recent exposed # definition encountered above is the one which # should lodge this dependency - is that true? state.add_c_dependency(current_def, node.spelling) new_artifact.add_dependency(node.spelling) return [new_artifact]
def run(self, artifacts: List[Artifact]) -> List[Artifact]: logger = logging.getLogger(__name__) if len(artifacts) == 1: artifact = artifacts[0] else: msg = ('Fortran Analyser expects only one Artifact, ' f'but was given {len(artifacts)}') raise TaskException(msg) reader = FileTextReader(artifact.location) new_artifact = Artifact(artifact.location, artifact.filetype, Analysed) state = FortranWorkingState(self.database) state.remove_fortran_file(reader.filename) normalised_source = FortranNormaliser(reader) scope: List[Tuple[str, str]] = [] for line in normalised_source.line_by_line(): logger.debug(scope) logger.debug('Considering: %s', line) if len(scope) == 0: unit_match: Optional[Match] \ = self._program_unit_pattern.match(line) if unit_match: unit_type: str = unit_match.group(1).lower() unit_name: str = unit_match.group(2).lower() logger.debug('Found %s called "%s"', unit_type, unit_name) unit_id = FortranUnitID(unit_name, reader.filename) state.add_fortran_program_unit(unit_id) new_artifact.add_definition(unit_name) scope.append((unit_type, unit_name)) continue use_match: Optional[Match] \ = self._use_pattern.match(line) if use_match: use_name: str = use_match.group(3).lower() if use_name in self._intrinsic_modules: logger.debug('Ignoring intrinsic module "%s"', use_name) else: if len(scope) == 0: use_message \ = '"use" statement found outside program unit' raise TaskException(use_message) logger.debug('Found usage of "%s"', use_name) unit_id = FortranUnitID(scope[0][1], reader.filename) state.add_fortran_dependency(unit_id, use_name) new_artifact.add_dependency(use_name) continue block_match: Optional[Match] = self._scoping_pattern.match(line) if block_match: # Beware we want the value of a different group to the one we # check the presence of. # block_name: str = block_match.group(1) \ and block_match.group(2).lower() block_nature: str = block_match.group(3).lower() logger.debug('Found %s called "%s"', block_nature, block_name) scope.append((block_nature, block_name)) continue proc_match: Optional[Match] \ = self._procedure_pattern.match(line) if proc_match: proc_nature = proc_match.group(1).lower() proc_name = proc_match.group(2).lower() logger.debug('Found %s called "%s"', proc_nature, proc_name) # Note: We append a tuple so double brackets. scope.append((proc_nature, proc_name)) continue iface_match: Optional[Match] = self._interface_pattern.match(line) if iface_match: iface_name = iface_match.group(1) \ and iface_match.group(1).lower() logger.debug('Found interface called "%s"', iface_name) scope.append(('interface', iface_name)) continue type_match: Optional[Match] = self._type_pattern.match(line) if type_match: type_name = type_match.group(3).lower() logger.debug('Found type called "%s"', type_name) scope.append(('type', type_name)) continue end_match: Optional[Match] = self._end_block_pattern.match(line) if end_match: end_nature: str = end_match.group(1) \ and end_match.group(1).lower() end_name: str = end_match.group(2) \ and end_match.group(2).lower() logger.debug('Found end of %s called %s', end_nature, end_name) exp: Tuple[str, str] = scope.pop() if end_nature is not None: if end_nature != exp[0]: end_message = 'Expected end of {exp} "{name}" ' \ 'but found {found}' end_values = { 'exp': exp[0], 'name': exp[1], 'found': end_nature } raise TaskException(end_message.format(**end_values)) if end_name is not None: if end_name != exp[1]: end_message = 'Expected end of {exp} "{name}" ' \ 'but found end of {found}' end_values = { 'exp': exp[0], 'name': exp[1], 'found': end_name } raise TaskException(end_message.format(**end_values)) return [new_artifact]
def run(self, artifacts: List[Artifact]) -> List[Artifact]: logger = logging.getLogger(__name__) if len(artifacts) == 1: artifact = artifacts[0] else: msg = ('Fortran Analyser expects only one Artifact, ' f'but was given {len(artifacts)}') raise TaskException(msg) reader = FileTextReader(artifact.location) new_artifact = Artifact(artifact.location, artifact.filetype, Analysed) state = FortranWorkingState(self.database) state.remove_fortran_file(reader.filename) logger.debug('Analysing: %s', reader.filename) # If this file defines any C symbol bindings it may also # end up with an entry in the C part of the database cstate = CWorkingState(self.database) cstate.remove_c_file(reader.filename) normalised_source = FortranNormaliser(reader) scope: List[Tuple[str, str]] = [] for line in normalised_source.line_by_line(): logger.debug(scope) logger.debug('Considering: %s', line) if len(scope) == 0: unit_match: Optional[Match] \ = self._program_unit_pattern.match(line) if unit_match is not None: unit_type: str = unit_match.group(1).lower() unit_name: str = unit_match.group(2).lower() logger.debug('Found %s called "%s"', unit_type, unit_name) unit_id = FortranUnitID(unit_name, reader.filename) state.add_fortran_program_unit(unit_id) new_artifact.add_definition(unit_name) scope.append((unit_type, unit_name)) continue use_match: Optional[Match] \ = self._use_pattern.match(line) if use_match is not None: use_name: str = use_match.group(3).lower() if use_name in self._intrinsic_modules: logger.debug('Ignoring intrinsic module "%s"', use_name) else: if len(scope) == 0: use_message \ = '"use" statement found outside program unit' raise TaskException(use_message) logger.debug('Found usage of "%s"', use_name) unit_id = FortranUnitID(scope[0][1], reader.filename) state.add_fortran_dependency(unit_id, use_name) new_artifact.add_dependency(use_name) continue block_match: Optional[Match] = self._scoping_pattern.match(line) if block_match is not None: # Beware we want the value of a different group to the one we # check the presence of. # block_name: str = block_match.group(1) \ and block_match.group(2).lower() block_nature: str = block_match.group(3).lower() logger.debug('Found %s called "%s"', block_nature, block_name) scope.append((block_nature, block_name)) continue proc_match: Optional[Match] \ = self._procedure_pattern.match(line) if proc_match is not None: proc_nature = proc_match.group(1).lower() proc_name = proc_match.group(2).lower() logger.debug('Found %s called "%s"', proc_nature, proc_name) scope.append((proc_nature, proc_name)) # Check for the procedure being symbol-bound to C cbind_match: Optional[Match] \ = self._cbind_pattern.match(line) if cbind_match is not None: cbind_name = cbind_match.group(2) # The name keyword on the bind statement is optional. # If it doesn't exist, the procedure name is used if cbind_name is None: cbind_name = proc_name cbind_name = cbind_name.lower().strip("'\"") logger.debug('Bound to C symbol "%s"', cbind_name) # A bind within an interface block means this is # exposure of a C-defined function to Fortran, # otherwise it is going the other way (allowing C # code to call the Fortran procedure) if any([stype == "interface" for stype, _ in scope]): # TODO: This is sort of hijacking the mechanism used # for Fortran module dependencies, only using the # symbol name. Longer term we probably need a more # elegant solution logger.debug('In an interface block; so a dependency') unit_id = FortranUnitID(scope[0][1], reader.filename) state.add_fortran_dependency(unit_id, cbind_name) new_artifact.add_dependency(cbind_name) else: # Add to the C database logger.debug('Not an interface block; so a definition') symbol_id = CSymbolID(cbind_name, reader.filename) cstate.add_c_symbol(symbol_id) new_artifact.add_definition(cbind_name) continue cbind_match = self._cbind_pattern.match(line) if cbind_match is not None: # This should be a line binding from C to a variable definition # (procedure binds are dealt with above) cbind_name = cbind_match.group(2) # The name keyword on the bind statement is optional. # If it doesn't exist, the Fortran variable name is used if cbind_name is None: var_search = re.search(r'.*::\s*(\w+)', line) if var_search: cbind_name = var_search.group(1) else: cbind_message \ = 'failed to find variable name ' \ 'on C bound variable' raise TaskException(cbind_message) cbind_name = cbind_name.lower().strip("'\"") logger.debug('Found C bound variable called "%s"', cbind_name) # Add to the C database symbol_id = CSymbolID(cbind_name, reader.filename) cstate.add_c_symbol(symbol_id) new_artifact.add_definition(cbind_name) iface_match: Optional[Match] = self._interface_pattern.match(line) if iface_match is not None: iface_name = iface_match.group(1) \ and iface_match.group(1).lower() logger.debug('Found interface called "%s"', iface_name) scope.append(('interface', iface_name)) continue type_match: Optional[Match] = self._type_pattern.match(line) if type_match is not None: type_name = type_match.group(3).lower() logger.debug('Found type called "%s"', type_name) scope.append(('type', type_name)) continue end_match: Optional[Match] = self._end_block_pattern.match(line) if end_match is not None: end_nature: str = end_match.group(1) \ and end_match.group(1).lower() end_name: str = end_match.group(2) \ and end_match.group(2).lower() logger.debug('Found end of %s called %s', end_nature, end_name) exp: Tuple[str, str] = scope.pop() if end_nature is not None: if end_nature != exp[0]: end_message = 'Expected end of {exp} "{name}" ' \ 'but found {found}' end_values = { 'exp': exp[0], 'name': exp[1], 'found': end_nature } raise TaskException(end_message.format(**end_values)) if end_name is not None: if end_name != exp[1]: end_message = 'Expected end of {exp} "{name}" ' \ 'but found end of {found}' end_values = { 'exp': exp[0], 'name': exp[1], 'found': end_name } raise TaskException(end_message.format(**end_values)) return [new_artifact]
def test_add_definition(self): test_path = Path('/test/path') artifact = Artifact(test_path, Unknown, New) artifact.add_definition("bar") assert artifact.defines == ["bar"]