コード例 #1
0
ファイル: common.py プロジェクト: stevewardle/fab
    def run(self, artifacts: List[Artifact]) -> List[Artifact]:
        logger = logging.getLogger(__name__)

        if len(artifacts) == 1:
            artifact = artifacts[0]
        else:
            msg = ('Header Analyser expects only one Artifact, '
                   f'but was given {len(artifacts)}')
            raise TaskException(msg)

        new_artifact = Artifact(artifact.location, artifact.filetype,
                                HeadersAnalysed)

        reader = FileTextReader(artifact.location)
        logger.debug('Looking for headers in: %s', reader.filename)
        for line in reader.line_by_line():
            include_match: Optional[Match] \
                = self._include_pattern.match(line)
            if include_match:
                include: str = include_match.group(1)
                logger.debug('Found header: %s', include)
                if include.startswith(('"', "'")):
                    include = include.strip('"').strip("'")
                    logger.debug('  * User header; adding dependency')
                    new_artifact.add_dependency(Path(self._workspace /
                                                     include))

        return [new_artifact]
コード例 #2
0
ファイル: c.py プロジェクト: MatthewHambley/fab
    def run(self, artifacts: List[Artifact]) -> List[Artifact]:
        logger = logging.getLogger(__name__)

        if len(artifacts) == 1:
            artifact = artifacts[0]
        else:
            msg = ('C Pragma Injector expects only one Artifact, '
                   f'but was given {len(artifacts)}')
            raise TaskException(msg)

        logger.debug('Injecting pragmas into: %s', artifact.location)
        injector = _CTextReaderPragmas(
            FileTextReader(artifact.location))

        output_file = self._workspace / artifact.location.name

        out_lines = [line for line in injector.line_by_line()]

        with output_file.open('w') as out_file:
            for line in out_lines:
                out_file.write(line)

        new_artifact = Artifact(output_file,
                                artifact.filetype,
                                Modified)
        for dependency in artifact.depends_on:
            new_artifact.add_dependency(dependency)

        return [new_artifact]
コード例 #3
0
    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]
コード例 #4
0
ファイル: c_test.py プロジェクト: uk-gov-mirror/metomi.fab
    def test_run(self, tmp_path):
        workspace = tmp_path / 'working'
        workspace.mkdir()

        test_file: Path = tmp_path / 'test.c'
        test_file.write_text(
            dedent('''
                   #include "user_include.h"
                   Unrelated text
                   #include 'another_user_include.h'
                   #include <system_include.h>
                   More unrelated text
                   #include <another_system_include.h>
                   '''))
        test_artifact = Artifact(test_file, CSource, HeadersAnalysed)
        test_artifact.add_dependency('foo')

        # Run the Injector
        injector = CPragmaInjector(workspace)
        artifacts_out = injector.run([test_artifact])

        assert len(artifacts_out) == 1
        assert artifacts_out[0].location == workspace / 'test.c'
        assert artifacts_out[0].filetype is CSource
        assert artifacts_out[0].state is Modified
        assert artifacts_out[0].depends_on == ['foo']
        assert artifacts_out[0].defines == []

        new_file = workspace / 'test.c'
        assert new_file.exists()
        with new_file.open('r') as fh:
            new_text = fh.read()

        expected_text = (dedent('''
                   #pragma FAB UsrIncludeStart
                   #include "user_include.h"
                   #pragma FAB UsrIncludeEnd
                   Unrelated text
                   #pragma FAB UsrIncludeStart
                   #include 'another_user_include.h'
                   #pragma FAB UsrIncludeEnd
                   #pragma FAB SysIncludeStart
                   #include <system_include.h>
                   #pragma FAB SysIncludeEnd
                   More unrelated text
                   #pragma FAB SysIncludeStart
                   #include <another_system_include.h>
                   #pragma FAB SysIncludeEnd
                   '''))

        assert new_text == expected_text
コード例 #5
0
ファイル: common.py プロジェクト: uk-gov-mirror/metomi.fab
    def run(self, artifacts: List[Artifact]) -> List[Artifact]:
        if len(artifacts) == 1:
            artifact = artifacts[0]
        else:
            msg = ('Header Analyser expects only one Artifact, '
                   f'but was given {len(artifacts)}')
            raise TaskException(msg)

        new_artifact = Artifact(artifact.location, artifact.filetype,
                                HeadersAnalysed)

        reader = FileTextReader(artifact.location)
        for line in reader.line_by_line():
            include_match: Optional[Match] \
                = self._include_pattern.match(line)
            if include_match:
                include: str = include_match.group(1)
                if include.startswith(('"', "'")):
                    include = include.strip('"').strip("'")
                    new_artifact.add_dependency(Path(self._workspace /
                                                     include))

        return [new_artifact]
コード例 #6
0
    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]
コード例 #7
0
ファイル: fortran.py プロジェクト: MatthewHambley/fab
    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]
コード例 #8
0
 def test_add_path_dependency(self):
     test_path = Path('/test/path')
     artifact = Artifact(test_path, Unknown, New)
     dep = Path('/path/to/bar')
     artifact.add_dependency(dep)
     assert artifact.depends_on == [dep]
コード例 #9
0
 def test_add_string_dependency(self):
     test_path = Path('/test/path')
     artifact = Artifact(test_path, Unknown, New)
     artifact.add_dependency("foo")
     assert artifact.depends_on == ["foo"]