Ejemplo n.º 1
0
class GNATcovPlugin(Module):

    # Keep this style name synchronized with Code_Coverage.GNATcov.

    PROJECT_ATTRIBUTES = [
        X(
            'project_attribute',
            package='IDE_Coverage',
            name='Gnatcov_Mode_Switches',
            label="Switches in 'gnatcov' mode",
            description=("Extra build switches to pass to the builder when in"
                         " 'gnatcov' mode."),
            editor_page='GNATcov',
            editor_section='Build',
            hide_in='wizard library_wizard',
        ).children(X('string')),
        X(
            'project_attribute',
            name='Level_Run',
            label='Coverage Level',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Run',
            hide_in='wizard library_wizard',
            description='The coverage level to pass to gnatcov run.',
        ).children(
            X('choice').children('branch'),
            X('choice').children('insn'),
            X('choice', default='true').children('stmt'),
            X('choice').children('stmt+decision'),
            X('choice').children('stmt+mcdc'),
        ),
        X(
            'project_attribute',
            name='Switches_Run',
            label='Extra switches',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Run',
            hide_in='wizard library_wizard',
            description='Extra build switches to pass to gnatcov run.',
        ).children(X('string')),
        X(
            'project_attribute',
            name='Level_Coverage',
            label='Coverage Level',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Coverage',
            hide_in='wizard library_wizard',
            description='The coverage level to pass to gnatcov coverage.',
        ).children(
            X('choice').children('branch'),
            X('choice').children('insn'),
            X('choice', default='true').children('stmt'),
            X('choice').children('stmt+decision'),
            X('choice').children('stmt+mcdc'),
        ),
        X(
            'project_attribute',
            name='Switches_Coverage',
            label='Extra switches',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Coverage',
            hide_in='wizard library_wizard',
            description='Extra build switches to pass to gnatcov coverage.',
        ).children(X('string')),
    ]

    BUILD_MODES = [
        X('builder-mode', name='gnatcov').children(
            X('description').children('Build with GNATcoverage information'),
            X('subdir').children('gnatcov'),
            X('supported-model').children('builder'),
            X('supported-model').children('gnatmake'),
            X('supported-model').children('gprbuild'),
            X('supported-model', filter='--subdirs=').children('gnatcov-run'),
            X('supported-model',
              filter='--subdirs=').children('gnatcov-coverage'),
            X('supported-model', filter='--subdirs=').children('gprclean'),
            X('supported-model',
              filter='--subdirs=').children('GNATtest execution mode'),
            X('extra-args', sections='-cargs:Ada -cargs:C').children(
                X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"),
                X('arg').children('--subdirs=%subdir'),
                X('arg', section='-cargs:Ada').children('-g'),
                X('arg', section='-cargs:Ada').children('-fdump-scos'),
                X('arg',
                  section='-cargs:Ada').children('-fpreserve-control-flow'),
                X('arg', section='-cargs:C').children('-g'),
                X('arg', section='-cargs:C').children('-fdump-scos'),
                X('arg',
                  section='-cargs:C').children('-fpreserve-control-flow'),
            ))
    ]

    BUILD_TARGETS = [
        X('target-model', name='gnatcov-build-main', category='').children(
            X('description').children('Build Main with the gnatcov switches'),
            X('command-line').children(
                X('arg').children('gprbuild')
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='2', lines='2'),
        ),

        X('target', model='gnatcov-build-main', category='_GNATcov_',
          name='GNATcov Build Main',
          menu=PLUGIN_MENU + '/Build/').children(
            X('target-type').children('executable'),
            X('in-toolbar').children('FALSE'),
            X('in-menu').children('TRUE'),
            X('read-only').children('TRUE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('%builder'),
                X('arg').children('-P%PP'),
                X('arg').children('%subdirsarg'),
                X('arg').children('-s'),
                X('arg').children('%X'),
                X('arg').children('-cargs:Ada'),
                X('arg').children('-g'),
                X('arg').children('-fdump-scos'),
                X('arg').children('-fpreserve-control-flow'),
                X('arg').children('-cargs:C'),
                X('arg').children('-g'),
                X('arg').children('-fdump-scos'),
                X('arg').children('-fpreserve-control-flow')
            )
        ),

        # Program execution under instrumented execution environment
        X('target-model', name='gnatcov-run', category='').children(
            X('description').children('Run under GNATcov for code coverage'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('run'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='1', lines='1')
        ),

        X('target', model='gnatcov-run', category='_GNATcov_',
          name='Run under GNATcov',
          menu=PLUGIN_MENU + '/Run/').children(
            X('target-type').children('executable'),
            X('in-toolbar').children('FALSE'),
            X('in-menu').children('TRUE'),
            X('read-only').children('TRUE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('run'),
                X('arg').children('-P%PP'),
                X('arg').children('--recursive'),
                X('arg').children('%target'),
                X('arg').children('-c'),
                X('arg').children("%attr(ide_coverage'level_run,stmt)"),
                X('arg').children('-o'),
                X('arg').children('%O%T.trace'),
                X('arg').children('%E'),
                X('arg').children("%attr(ide_coverage'switches_run)"),
                X('arg').children('%X'),
            ),
        ),

        X('target', model='gnatcov-run', category='_GNATcov_',
          name='Run GNATcov with instrumentation').children(
            X('target-type').children('executable'),
            X('in-toolbar').children('FALSE'),
            X('in-menu').children('FALSE'),
            X('read-only').children('TRUE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('instrument'),
                X('arg').children('-P%PP'),
                X('arg').children('%subdirsarg'),
                X('arg').children('--level'),
                X('arg').children("%attr(ide_coverage'level_run,stmt)"),
                X('arg').children('--dump-trigger=atexit'),
                X('arg').children('%X'),
            ),
        ),

        X('target', model='gnatcov-build-main',
          category='_GNATcov_',
          name='GNATcov Build Coverage Runtime').children(
            X('target-type').children('executable'),
            X('in-toolbar').children('FALSE'),
            X('in-menu').children('FALSE'),
            X('read-only').children('TRUE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('%builder'),
                X('arg').children('-f'),
                X('arg').children(
                    '%python' + \
                    '(gnatcov.GNATcovPlugin.' + \
                    'get_coverage_runtime_project_arg())'),
                X('arg').children(
                    '%python' + \
                    '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'),
            )
        ),

        X('target', model='gnatcov-build-main',
          category='_GNATcov_',
          name='GNATcov Install Coverage Runtime').children(
            X('target-type').children('executable'),
            X('in-toolbar').children('FALSE'),
            X('in-menu').children('FALSE'),
            X('read-only').children('TRUE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('gprinstall'),
                X('arg').children('-f'),
                X('arg').children('-p'),
                X('arg').children(
                    '%python' + \
                    '(gnatcov.GNATcovPlugin.' + \
                    'get_coverage_runtime_project_arg())'),
                X('arg').children(
                    '%python' + \
                    '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'),
                X('arg').children(
                    '%python' + \
                    '(gnatcov.GNATcovPlugin.get_prefix_arg())')
            )
        ),

        X('target', model='gnatcov-build-main', category='_GNATcov_',
          name='GNATcov Build Instrumented Main').children(
            X('target-type').children('executable'),
            X('in-toolbar').children('FALSE'),
            X('in-menu').children('FALSE'),
            X('read-only').children('TRUE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('%builder'),
                X('arg').children('-f'),
                X('arg').children('-p'),
                X('arg').children('-P%PP'),
                X('arg').children('%subdirsarg'),
                X('arg').children('--src-subdirs=gnatcov-instr'),
                X('arg').children(
                    '%python' + \
                    '(gnatcov.GNATcovPlugin.' + \
                    'get_installed_coverage_runtime_project_arg())')
            )
        ),

        # Coverage report generation
        X('target-model', name='gnatcov-coverage', category='').children(
            X('description').children('Code coverage with GNATcov'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('coverage'),
                X('arg').children('-P%PP'),
                X('arg').children('--recursive'),
                X('arg').children('%target'),
                X('arg').children('--annotate=xcov'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='1', lines='4'),
        ),

        X('target', model='gnatcov-coverage', category='_GNATcov_',
            name='Generate GNATcov Main Report',
            menu=PLUGIN_MENU + '/Generate Report/').children(
            X('target-type').children('executable'),
            X('in-toolbar').children('FALSE'),
            X('in-menu').children('TRUE'),
            X('read-only').children('TRUE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('coverage'),
                X('arg').children('-P%PP'),
                X('arg').children('--recursive'),
                X('arg').children('%target'),
                X('arg').children('-c'),
                X('arg').children("%attr(ide_coverage'level_coverage,stmt)"),
                X('arg').children('--annotate=xcov+'),
                X('arg').children('--output-dir=%O'),
                X('arg').children('-T'),
                X('arg').children('%O%T.trace'),
                X('arg').children("%attr(ide_coverage'switches_coverage)"),
                X('arg').children('%X'),
            ),
        ),

        X('target', model='gnatcov-coverage', category='_GNATcov_',
            name='Generate GNATcov Instrumented Main Report').children(
            X('target-type').children('executable'),
            X('read-only').children('TRUE'),
            X('in-menu').children('FALSE'),
            X('output-parsers').children(
                'output_chopper utf_converter console_writer end_of_build'),
            X('iconname').children('gps-build-all-symbolic'),
            X('launch-mode').children('MANUALLY'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('coverage'),
                X('arg').children('-P%PP'),
                X('arg').children('%subdirsarg'),
                X('arg').children('%target'),
                X('arg').children('-c'),
                X('arg').children("%attr(ide_coverage'level_coverage,stmt)"),
                X('arg').children('--annotate=xcov+'),
                X('arg').children('--output-dir=%O'),
                X('arg').children('-T'),
                X('arg').children('%O%T.srctrace'),
                X('arg').children("%attr(ide_coverage'switches_coverage)"),
                X('arg').children('%X'),
            ),

        ),
    ]

    GNATCOV_DOCUMENTATION = [
        X('doc_path').children(
            os.path.join(gnatcov_install_dir, 'share', 'doc', 'gnatcoverage',
                         'html') if gnatcov_install_dir else None),
        X('documentation_file').children(
            X('name').children('gnatcov.html'),
            X('descr').children("GNATcoverage User's Guide"),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                "/Help/GNATcoverage/GNATcoverage User's Guide"),
        ),
    ]

    GNATEMU_DOCUMENTATION = [
        X('doc_path').children('share/doc/gnatemu/html'),
        X('documentation_file').children(
            X('name').children('gnatemulator.html'),
            X('descr').children('GNATemulator Documentation'),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                '/Help/GNATcoverage/GNATemulator Documentation'),
        ),
    ]

    __run_gnatcov_wf_build_target = None
    # The 'Run GNATcoverage' workflow Build Target

    __run_gnatcov_instr_wf_build_target = None

    # The 'Run GNATcoverage with instrumentation' workflow Build Target

    def setup(self):
        # This plugin makes sense only if GNATcoverage is available.
        if not self.is_gnatcov_available():
            return

        # Create all custom things that do not require GPS' GUI to be ready
        # (i.e.: all but menus and hooks).
        for xml_nodes in (
                self.PROJECT_ATTRIBUTES,
                self.BUILD_MODES,
                self.GNATCOV_DOCUMENTATION,
                self.GNATEMU_DOCUMENTATION,
        ):
            GPS.parse_xml(list_to_xml(xml_nodes))

        # Update the GNATcoverage workflow Build Targets, creating them and
        # showing/hiding them appropriately
        self.update_worflow_build_targets()

        # Now the parent menu is present, fill it with custom targets.
        GPS.parse_xml(list_to_xml(self.BUILD_TARGETS))

        GPS.Hook('compilation_finished').add(self.on_compilation_finished)

    def project_view_changed(self):
        if self.is_gnatcov_available():
            self.update_worflow_build_targets()

    def update_worflow_build_targets(self):
        gnatcov_available = self.is_gnatcov_available()
        instrumentation_supported = self.is_instrumentation_supported()

        if gnatcov_available and not self.__run_gnatcov_wf_build_target:
            workflows.create_target_from_workflow(
                target_name="Run GNATcoverage",
                workflow_name="run-gnatcov",
                workflow=self.run_gnatcov_wf,
                icon_name="gps-run-gnatcov-symbolic",
                parent_menu="/Build/Workflow/GNATcov/")

            self.__run_gnatcov_wf_build_target = \
                GPS.BuildTarget("Run GNATcoverage")

            if instrumentation_supported:
                workflows.create_target_from_workflow(
                    target_name="Run GNATcoverage with instrumentation",
                    workflow_name="run-gnatcov-with-instrumentation",
                    in_toolbar=False,
                    workflow=self.run_gnatcov_with_instrumentation_wf,
                    parent_menu=PLUGIN_MENU + "/Intrumentation/")

                self.__run_gnatcov_instr_wf_build_target = \
                    GPS.BuildTarget("Run GNATcoverage with instrumentation")

        if not gnatcov_available:
            if self.__run_gnatcov_wf_build_target:
                self.__run_gnatcov_wf_build_target.hide()

            if self.__run_gnatcov_instr_wf_build_target:
                self.__run_gnatcov_instr_wf_build_target.hide()

        elif not instrumentation_supported:
            if self.__run_gnatcov_instr_wf_build_target:
                self.__run_gnatcov_instr_wf_build_target.hide()

    def is_gnatcov_available(self):
        return os_utils.locate_exec_on_path('gnatcov') != ""

    def is_instrumentation_supported(self):
        # Check if GNATcov and GPRbuild are recent enough (after the 21
        # release)
        for exe in 'gnatcov', 'gprbuild':

            try:
                version_out = GPS.Process(
                    exe + " --version").get_result().splitlines()[0]

                matches = TOOL_VERSION_REGEXP.findall(version_out)
                version_major, version_minor = matches[0]

            except Exception:
                # Can happen with the GS testuite if we use a fake gnatcov exe
                return False

            if not (int(version_major) >= 22 or
                    (int(version_major) == 21 and version_minor[-1] != "w")):
                GPS.Logger("GNATCOVERAGE").log("instrumentation mode not " +
                                               "supported due to an older %s" %
                                               exe)
                return False

        return True

    def run_gnatcov_wf(self, main_name):
        # Build the project with GNATcov switches

        p = promises.TargetWrapper("GNATcov Build Main")
        r = yield p.wait_on_execute()
        if r is not 0:
            GPS.Console("Messages").write("Can't build the project with " +
                                          "the GNATcov switches",
                                          mode="error")
            return

        # Get the executable to analyze
        exe = str(GPS.File(main_name).executable_path)

        # Run GNATcov on it
        p = promises.TargetWrapper("Run under GNATcov")
        r = yield p.wait_on_execute(exe)
        if r is not 0:
            GPS.Console("Messages").write("GNATcov run failed ", mode="error")
            return

        # Generate and display the GNATcov Coverage Report
        p = promises.TargetWrapper("Generate GNATcov Main Report")
        r = yield p.wait_on_execute(exe)

    def run_gnatcov_with_instrumentation_wf(self, main_name):

        # Get the executable to analyze
        exe = str(GPS.File(main_name).executable_path)

        # Build the coverage runtime
        p = promises.TargetWrapper("GNATcov Build Coverage Runtime")
        r = yield p.wait_on_execute(quiet=True)
        if r is not 0:
            GPS.Console("Messages").write("GNATcov runtime build failed ",
                                          mode="error")
            return

        # Install the coverage runtime
        p = promises.TargetWrapper("GNATcov Install Coverage Runtime")
        r = yield p.wait_on_execute(quiet=True)
        if r is not 0:
            GPS.Console("Messages").write("GNATcov runtime build failed ",
                                          mode="error")
            return
        # Run GNATcov with instrumentation on it
        p = promises.TargetWrapper("Run GNATcov with instrumentation")
        r = yield p.wait_on_execute(exe, quiet=True)
        if r is not 0:
            GPS.Console("Messages").write("GNATcov run failed ", mode="error")
            return

        # Build the instrumented main
        p = promises.TargetWrapper("GNATcov Build Instrumented Main")
        r = yield p.wait_on_execute(quiet=True)
        if r is not 0:
            GPS.Console("Messages").write("Can't build the project with " +
                                          "the GNATcov switches",
                                          mode="error")
            return

        # Go to the object directory before executing the instrumented main: we
        # want to produce the trace file in the object dir and not in the
        # project's root directory
        obj_dir = GPS.Project.root().object_dirs()[0]
        GPS.cd(obj_dir)

        # Build the instrumented main
        p = promises.ProcessWrapper(cmdargs=[exe])
        r = yield p.wait_until_terminate()

        # Generate and display the GNATcov Coverage Report
        p = promises.TargetWrapper("Generate GNATcov Instrumented Main Report")
        r = yield p.wait_on_execute(exe, quiet=True)

    def reload_gnatcov_data(self):
        """Clean the coverage report and reload it from the files."""

        # If needed, switch to GNATcov build mode.
        pref_name = ("Coverage-Toolchain" if "ENABLE_GCOV" in os.environ else
                     "Coverage-Toolchain-Internal")

        if GPS.Preference(pref_name).get() != 'Gnatcov':
            GPS.Preference(pref_name).set('Gnatcov')

        GPS.execute_action("coverage clear from memory")

        if GPS.Project.root().is_harness_project():
            a = GPS.CodeAnalysis.get("Coverage Report")
            original = GPS.Project.root().original_project().file()
            a.add_gcov_project_info(original)
        else:
            GPS.execute_action("coverage load data for all projects")

    def on_compilation_finished(self,
                                hook,
                                category,
                                target_name="",
                                mode_name="",
                                status="",
                                *args):
        """Called whenever a compilation ends."""

        # If compilation failed, do nothing.
        if status:
            return

        if target_name in [
                "Generate GNATcov Main Report",
                "Generate GNATcov Instrumented Main Report"
        ]:
            self.reload_gnatcov_data()

    @staticmethod
    def get_coverage_runtime_project_arg():
        """
        Return the path of the coverage runtime bundled with the gnatcov
        installation.
        This runtime is needed to use gnatcov with instrumentation.
        """
        gnatcov_path = os_utils.locate_exec_on_path('gnatcov')
        gnatcov_dir = os.path.dirname(gnatcov_path)
        runtime_dir = os.path.join(gnatcov_dir, os.pardir, "share",
                                   "gnatcoverage", "gnatcov_rts")

        return "-P" + os.path.join(runtime_dir, "gnatcov_rts_full.gpr")

    @staticmethod
    def get_relocate_build_tree_arg():
        return "--relocate-build-tree=" + tempfile.gettempdir()

    @staticmethod
    def get_prefix_arg():
        return "--prefix=" + tempfile.gettempdir()

    @staticmethod
    def get_installed_coverage_runtime_project_arg():
        return "--implicit-with=" + \
            os.path.join(tempfile.gettempdir(), "share",
                         "gpr", "gnatcov_rts_full.gpr")
Ejemplo n.º 2
0
class GNATcovPlugin(object):

    PLUGIN_MENU = '/Analyze/Coverage/GNATcoverage'

    # Keep this style name synchronized with Code_Coverage.GNATcov.

    PROJECT_ATTRIBUTES = [
        X(
            'project_attribute',
            package='IDE_Coverage',
            name='Gnatcov_Mode_Switches',
            label="Switches in 'gnatcov' mode",
            description=("Extra build switches to pass to the builder when in"
                         " 'gnatcov' mode."),
            editor_page='GNATcov',
            editor_section='Build',
            hide_in='wizard library_wizard',
        ).children(X('string')),
        X(
            'project_attribute',
            name='Level_Run',
            label='Coverage Level',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Run',
            hide_in='wizard library_wizard',
            description='The coverage level to pass to gnatcov run.',
        ).children(
            X('choice').children('branch'),
            X('choice').children('insn'),
            X('choice', default='true').children('stmt'),
            X('choice').children('stmt+decision'),
            X('choice').children('stmt+mcdc'),
        ),
        X(
            'project_attribute',
            name='Switches_Run',
            label='Extra switches',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Run',
            hide_in='wizard library_wizard',
            description='Extra build switches to pass to gnatcov run.',
        ).children(X('string')),
        X(
            'project_attribute',
            name='Level_Coverage',
            label='Coverage Level',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Coverage',
            hide_in='wizard library_wizard',
            description='The coverage level to pass to gnatcov coverage.',
        ).children(
            X('choice').children('branch'),
            X('choice').children('insn'),
            X('choice', default='true').children('stmt'),
            X('choice').children('stmt+decision'),
            X('choice').children('stmt+mcdc'),
        ),
        X(
            'project_attribute',
            name='Switches_Coverage',
            label='Extra switches',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Coverage',
            hide_in='wizard library_wizard',
            description='Extra build switches to pass to gnatcov coverage.',
        ).children(X('string')),
    ]

    BUILD_MODES = [
        X('builder-mode', name='gnatcov').children(
            X('description').children('Build with GNATcoverage information'),
            X('subdir').children('gnatcov'),
            X('supported-model').children('builder'),
            X('supported-model').children('gnatmake'),
            X('supported-model').children('gprbuild'),
            X('supported-model', filter='--subdirs=').children('gnatcov-run'),
            X('supported-model',
              filter='--subdirs=').children('gnatcov-coverage'),
            X('supported-model', filter='--subdirs=').children('gprclean'),
            X('supported-model',
              filter='--subdirs=').children('GNATtest execution mode'),
            X('extra-args', sections='-cargs:Ada -cargs:C').children(
                X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"),
                X('arg').children('--subdirs=%subdir'),
                X('arg', section='-cargs:Ada').children('-g'),
                X('arg', section='-cargs:Ada').children('-fdump-scos'),
                X('arg',
                  section='-cargs:Ada').children('-fpreserve-control-flow'),
                X('arg', section='-cargs:C').children('-g'),
                X('arg', section='-cargs:C').children('-fdump-scos'),
                X('arg',
                  section='-cargs:C').children('-fpreserve-control-flow'),
            ))
    ]

    BUILD_TARGETS = [
        X('target-model', name='gnatcov-build-main', category='').children(
            X('description').children('Build Main with the gnatcov switches'),
            X('command-line').children(X('arg').children('gprbuild')),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='2', lines='2'),
        ),
        X('target',
          model='gnatcov-build-main',
          category='_GNATcov_',
          name='GNATcov Build Main',
          menu=PLUGIN_MENU + '/Build/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('%builder'),
                  X('arg').children('-P%PP'),
                  X('arg').children('%subdirsarg'),
                  X('arg').children('-s'),
                  X('arg').children('-cargs:Ada'),
                  X('arg').children('-g'),
                  X('arg').children('-fdump-scos'),
                  X('arg').children('-fpreserve-control-flow'),
                  X('arg').children('-cargs:C'),
                  X('arg').children('-g'),
                  X('arg').children('-fdump-scos'),
                  X('arg').children('-fpreserve-control-flow'))),

        # Program execution under instrumented execution environment
        X('target-model', name='gnatcov-run', category='').children(
            X('description').children('Run under GNATcov for code coverage'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('run'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='1', lines='1')),
        X('target',
          model='gnatcov-run',
          category='_GNATcov_',
          name='Run under GNATcov',
          menu=PLUGIN_MENU + '/Run/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('run'),
                  X('arg').children('-P%PP'),
                  X('arg').children('--recursive'),
                  X('arg').children('%target'),
                  X('arg').children('-c'),
                  X('arg').children("%attr(ide_coverage'level_run,stmt)"),
                  X('arg').children('-o'),
                  X('arg').children('%TT.trace'),
                  X('arg').children('%E'),
                  X('arg').children("%attr(ide_coverage'switches_run)"),
              ),
          ),

        # Coverage report generation
        X('target-model', name='gnatcov-coverage', category='').children(
            X('description').children('Code coverage with GNATcov'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('coverage'),
                X('arg').children('-P%PP'),
                X('arg').children('--recursive'),
                X('arg').children('%target'),
                X('arg').children('--annotate=xcov'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='1', lines='4'),
        ),
        X('target',
          model='gnatcov-coverage',
          category='_GNATcov_',
          name='Generate GNATcov Main Report',
          menu=PLUGIN_MENU + '/Generate Report/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('coverage'),
                  X('arg').children('-P%PP'),
                  X('arg').children('--recursive'),
                  X('arg').children('%target'),
                  X('arg').children('-c'),
                  X('arg').children("%attr(ide_coverage'level_coverage,stmt)"),
                  X('arg').children('--annotate=xcov+'),
                  X('arg').children('--output-dir=%O'),
                  X('arg').children('-T'),
                  X('arg').children('%TT.trace'),
                  X('arg').children("%attr(ide_coverage'switches_coverage)"),
              ),
          ),
    ]

    GNATCOV_DOCUMENTATION = [
        X('doc_path').children(
            os.path.join(gnatcov_install_dir, 'share', 'doc', 'gnatcoverage',
                         'html') if gnatcov_install_dir else None),
        X('documentation_file').children(
            X('name').children('gnatcov.html'),
            X('descr').children("GNATcoverage User's Guide"),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                "/Help/GNATcoverage/GNATcoverage User's Guide"),
        ),
    ]

    GNATEMU_DOCUMENTATION = [
        X('doc_path').children('share/doc/gnatemu/html'),
        X('documentation_file').children(
            X('name').children('gnatemulator.html'),
            X('descr').children('GNATemulator Documentation'),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                '/Help/GNATcoverage/GNATemulator Documentation'),
        ),
    ]

    def __init__(self):
        # Create all custom things that do not require GPS' GUI to be ready
        # (i.e.: all but menus and hooks).
        for xml_nodes in (
                self.PROJECT_ATTRIBUTES,
                self.BUILD_MODES,
                self.GNATCOV_DOCUMENTATION,
                self.GNATEMU_DOCUMENTATION,
        ):
            GPS.parse_xml(list_to_xml(xml_nodes))

        # Create the GNATcoverage toolbar button
        self.create_toolbar_button()

        # Defer further initialization to when GPS is completely ready.
        GPS.Hook('gps_started').add(self.on_gps_started)

    def create_toolbar_button(self):
        workflows.create_target_from_workflow(
            target_name="Run GNATcoverage",
            workflow_name="run-gnatcov",
            workflow=self.run_gnatcov_wf,
            icon_name="gps-run-gnatcov-symbolic",
            parent_menu="/Build/Workflow/GNATcov/")

    def run_gnatcov_wf(self, main_name):
        # Build the project with GNATcov switches

        p = promises.TargetWrapper("GNATcov Build Main")
        r = yield p.wait_on_execute()
        if r is not 0:
            GPS.Console("Messages").write("Can't build the project with " +
                                          "the GNATcov switches",
                                          mode="error")
            return

        # Get the executable to analyze
        exe = str(GPS.File(main_name).executable_path)

        # Run GNATcov on it
        p = promises.TargetWrapper("Run under GNATcov")
        r = yield p.wait_on_execute(exe)
        if r is not 0:
            GPS.Console("Messages").write("GNATcov run failed ", mode="error")
            return

        # Generate and display the GNATcov Coverage Report
        p = promises.TargetWrapper("Generate GNATcov Main Report")
        r = yield p.wait_on_execute(exe)

    def on_gps_started(self, hook):
        # Now the parent menu is present, fill it with custom targets.
        GPS.parse_xml(list_to_xml(self.BUILD_TARGETS))

        GPS.Hook('compilation_finished').add(self.on_compilation_finished)

    def reload_gnatcov_data(self):
        """Clean the coverage report and reload it from the files."""

        # If needed, switch to GNATcov build mode.
        pref_name = ("Coverage-Toolchain" if "ENABLE_GCOV" in os.environ else
                     "Coverage-Toolchain-Internal")

        if GPS.Preference(pref_name).get() != 'Gnatcov':
            GPS.Preference(pref_name).set('Gnatcov')

        GPS.execute_action("coverage clear from memory")

        if GPS.Project.root().is_harness_project():
            a = GPS.CodeAnalysis.get("Coverage Report")
            original = GPS.Project.root().original_project().file()
            a.add_gcov_project_info(original)
        else:
            GPS.execute_action("coverage load data for all projects")

    def on_compilation_finished(self,
                                hook,
                                category,
                                target_name="",
                                mode_name="",
                                status="",
                                *args):
        """Called whenever a compilation ends."""

        # If compilation failed, do nothing.
        if status:
            return

        if target_name in ["Generate GNATcov Main Report"]:
            self.reload_gnatcov_data()
Ejemplo n.º 3
0
class GNATcovPlugin(object):

    PLUGIN_MENU = '/Tools/GNATcov/'

    # Keep this style name synchronized with Code_Coverage.GNATcov.

    PROJECT_ATTRIBUTES = [
        X(
            'project_attribute',
            package='IDE_Coverage',
            name='Gnatcov_Mode_Switches',
            label="Switches in 'gnatcov' mode",
            description=("Extra build switches to pass to the builder when in"
                         " 'gnatcov' mode."),
            editor_page='GNATcov',
            editor_section='Build',
            hide_in='wizard library_wizard',
        ).children(X('string')),
        X(
            'project_attribute',
            name='Level_Run',
            label='Coverage Level',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Run',
            hide_in='wizard library_wizard',
            description='The coverage level to pass to gnatcov run.',
        ).children(
            X('choice').children('branch'),
            X('choice').children('insn'),
            X('choice', default='true').children('stmt'),
            X('choice').children('stmt+decision'),
            X('choice').children('stmt+mcdc'),
        ),
        X(
            'project_attribute',
            name='Switches_Run',
            label='Extra switches',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Run',
            hide_in='wizard library_wizard',
            description='Extra build switches to pass to gnatcov run.',
        ).children(X('string')),
        X(
            'project_attribute',
            name='Level_Coverage',
            label='Coverage Level',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Coverage',
            hide_in='wizard library_wizard',
            description='The coverage level to pass to gnatcov coverage.',
        ).children(
            X('choice').children('branch'),
            X('choice').children('insn'),
            X('choice', default='true').children('stmt'),
            X('choice').children('stmt+decision'),
            X('choice').children('stmt+mcdc'),
        ),
        X(
            'project_attribute',
            name='Switches_Coverage',
            label='Extra switches',
            package='IDE_Coverage',
            editor_page='GNATcov',
            editor_section='Coverage',
            hide_in='wizard library_wizard',
            description='Extra build switches to pass to gnatcov coverage.',
        ).children(X('string')),
    ]

    BUILD_MODES = [
        X('builder-mode', name='gnatcov').children(
            X('description').children('Build with GNATcoverage information'),
            X('subdir').children('gnatcov'),
            X('supported-model').children('builder'),
            X('supported-model').children('gnatmake'),
            X('supported-model').children('gprbuild'),
            X('supported-model', filter='--subdirs=').children('gnatcov-run'),
            X('supported-model',
              filter='--subdirs=').children('gnatcov-coverage'),
            X('supported-model', filter='--subdirs=').children('gprclean'),
            X('extra-args', sections='-cargs').children(
                X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"),
                X('arg').children('--subdirs=%subdir'),
                X('arg', section='-cargs').children('-g'),
                X('arg', section='-cargs').children('-fdump-scos'),
                X('arg', section='-cargs').children('-fpreserve-control-flow'),
            ))
    ]

    BUILD_TARGETS = [
        # Program execution under instrumented execution environment
        X('target-model', name='gnatcov-run', category='').children(
            X('description').children('Run under GNATcov for code coverage'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('run'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='2', lines='2'),
        ),
        X('target',
          model='gnatcov-run',
          category='GNATcov run',
          name='Run under GNATcov',
          menu=PLUGIN_MENU).children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('run'),
                  X('arg').children('-P%PP'),
                  X('arg').children('--recursive'),
                  X('arg').children('%target'),
                  X('arg').children('-c'),
                  X('arg').children("%attr(ide_coverage'level_run,stmt)"),
                  X('arg').children('-o'),
                  X('arg').children('%TT.trace'),
                  X('arg').children('%E'),
                  X('arg').children("%attr(ide_coverage'switches_run)"),
              ),
          ),

        # Coverage report generation
        X('target-model', name='gnatcov-coverage', category='').children(
            X('description').children('Code coverage with GNATcov'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('coverage'),
                X('arg').children('-P%PP'),
                X('arg').children('--recursive'),
                X('arg').children('%target'),
                X('arg').children('--annotate=xcov'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='1', lines='4'),
        ),
        X('target',
          model='gnatcov-coverage',
          category='GNATcov coverage',
          name='Generate GNATcov Main Report',
          menu=PLUGIN_MENU).children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('coverage'),
                  X('arg').children('-P%PP'),
                  X('arg').children('--recursive'),
                  X('arg').children('%target'),
                  X('arg').children('-c'),
                  X('arg').children("%attr(ide_coverage'level_coverage,stmt)"),
                  X('arg').children('--annotate=xcov+'),
                  X('arg').children('--output-dir=%O'),
                  X('arg').children('-T'),
                  X('arg').children('%TT.trace'),
                  X('arg').children("%attr(ide_coverage'switches_coverage)"),
              ),
          ),
    ]

    GNATCOV_DOCUMENTATION = [
        X('doc_path').children(
            os.path.join(gnatcov_install_dir, 'share', 'doc', 'gnatcoverage',
                         'html') if gnatcov_install_dir else None),
        X('documentation_file').children(
            X('name').children('gnatcov.html'),
            X('descr').children("GNATcoverage User's Guide"),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                "/Help/GNATcoverage/GNATcoverage User's Guide"),
        ),
    ]

    GNATEMU_DOCUMENTATION = [
        X('doc_path').children('share/doc/gnatemu/html'),
        X('documentation_file').children(
            X('name').children('gnatemulator.html'),
            X('descr').children('GNATemulator Documentation'),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                '/Help/GNATcoverage/GNATemulator Documentation'),
        ),
    ]

    def __init__(self):
        # Create all custom things that do not require GPS' GUI to be ready
        # (i.e.: all but menus and hooks).
        for xml_nodes in (
                self.PROJECT_ATTRIBUTES,
                self.BUILD_MODES,
                self.GNATCOV_DOCUMENTATION,
                self.GNATEMU_DOCUMENTATION,
        ):
            GPS.parse_xml(list_to_xml(xml_nodes))

        # Defer further initialization to when GPS is completely ready.
        GPS.Hook('gps_started').add(self.on_gps_started)

    def on_gps_started(self, hook):
        # Now the parent menu is present, fill it with custom targets.
        GPS.parse_xml(list_to_xml(self.BUILD_TARGETS))

        GPS.Hook('compilation_finished').add(self.on_compilation_finished)

    def reload_gnatcov_data(self):
        """Clean the coverage report and reload it from the files."""

        # If needed, switch to GNATcov build mode.
        if GPS.Preference("Coverage-Toolchain").get() != 'Gnatcov':
            GPS.Preference("Coverage-Toolchain").set('Gnatcov')

        GPS.execute_action("/Tools/Coverage/Clear coverage from memory")

        if GPS.Project.root().is_harness_project():
            a = GPS.CodeAnalysis.get("Coverage Report")
            original = GPS.Project.root().original_project().file()
            a.add_gcov_project_info(original)
        else:
            GPS.execute_action("/Tools/Coverage/Load data for all projects")

    def on_compilation_finished(self,
                                hook,
                                category,
                                target_name="",
                                mode_name="",
                                status=""):
        """Called whenever a compilation ends."""

        # If compilation failed, do nothing.
        if status:
            return

        if target_name in ["Generate GNATcov Main Report"]:
            self.reload_gnatcov_data()
Ejemplo n.º 4
0
class GNATcovPlugin(Module):

    # Keep this style name synchronized with Code_Coverage.GNATcov.

    BUILD_MODES = [
        X('builder-mode', name='gnatcov').children(
            X('description').children('Build with GNATcoverage information'),
            X('subdir').children('gnatcov'),
            X('supported-model').children('builder'),
            X('supported-model').children('gnatmake'),
            X('supported-model').children('gprbuild'),
            X('supported-model', filter='--subdirs=').children('gnatcov-run'),
            X('supported-model',
              filter='--subdirs=').children('gnatcov-coverage'),
            X('supported-model', filter='--subdirs=').children('gprclean'),
            X('supported-model',
              filter='--subdirs=').children('GNATtest execution mode'),
            X('extra-args', sections='-cargs:Ada -cargs:C').children(
                X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"),
                X('arg').children('--subdirs=%subdir'),
                X('arg', section='-cargs:Ada').children('-g'),
                X('arg', section='-cargs:Ada').children('-fdump-scos'),
                X('arg',
                  section='-cargs:Ada').children('-fpreserve-control-flow'),
                X('arg', section='-cargs:C').children('-g'),
                X('arg', section='-cargs:C').children('-fdump-scos'),
                X('arg',
                  section='-cargs:C').children('-fpreserve-control-flow'),
            ))
    ]

    BUILD_TARGET_MODELS = [
        X('target-model', name='gnatcov-build-main', category='').children(
            X('description').children('Build Main with the gnatcov switches'),
            X('command-help').children('{help}'),
            X('command-line').children(X('arg').children('gprbuild')),
            X('iconname').children('gps-build-all-symbolic'),
            X('switches', command='%(tool_name)s', columns='2', lines='2'),
        ),

        # Program execution under instrumented execution environment
        X('target-model', name='gnatcov-run', category='').children(
            X('description').children('Run under GNATcov for code coverage'),
            X('command-help').children('{help}'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('run'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X("switches", command="%(tool_name)s", columns="1",
              lines="1").children(
                  X("combo",
                    line="1",
                    column="1",
                    noswitch="stmt",
                    separator="=",
                    switch="--level",
                    label="Coverage Level",
                    tip="""The coverage level to pass to gnatcov run.""").
                  children(
                      X('combo-entry', label='branch', value='branch'),
                      X('combo-entry', label='insn', value='insn'),
                      X('combo-entry', label='stmt', value='stmt'),
                      X('combo-entry',
                        label='stmt+decision',
                        value='stmt+decision'),
                      X('combo-entry', label='stmt+mcdc', value='stmt+mcdc'),
                  ), ),
        ),
        # Coverage report generation
        X('target-model', name='gnatcov-coverage', category='').children(
            X('description').children('Code coverage with GNATcov'),
            X('command-help').children('{help}'),
            X('command-line').children(
                X('arg').children('gnatcov'),
                X('arg').children('coverage'),
                X('arg').children('%X'),
                X('arg').children('-P%PP'),
                X('arg').children('--recursive'),
                X('arg').children('%target'),
                X('arg').children('--annotate=xcov'),
            ),
            X('iconname').children('gps-build-all-symbolic'),
            X("switches", command="%(tool_name)s", columns="1",
              lines="1").children(
                  X("combo",
                    line="1",
                    column="1",
                    noswitch="stmt",
                    separator="=",
                    switch="--level",
                    label="Coverage Level",
                    tip="""The coverage level to pass to gnatcov coverage.""").
                  children(
                      X('combo-entry', label='branch', value='branch'),
                      X('combo-entry', label='insn', value='insn'),
                      X('combo-entry', label='stmt', value='stmt'),
                      X('combo-entry',
                        label='stmt+decision',
                        value='stmt+decision'),
                      X('combo-entry', label='stmt+mcdc', value='stmt+mcdc'),
                  ), ),
        ),
    ]

    BINARY_TRACES_BUILD_TARGETS = [
        X('target',
          model='gnatcov-build-main',
          category='_GNATcov_',
          name='GNATcov Build Main',
          menu=BINARY_TRACES_MENU + '/Build/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('%builder'),
                  X('arg').children('-P%PP'),
                  X('arg').children('%subdirsarg'),
                  X('arg').children('-s'),
                  X('arg').children('%X'),
                  X('arg').children('-cargs:Ada'),
                  X('arg').children('-g'),
                  X('arg').children('-fdump-scos'),
                  X('arg').children('-fpreserve-control-flow'),
                  X('arg').children('-cargs:C'),
                  X('arg').children('-g'),
                  X('arg').children('-fdump-scos'),
                  X('arg').children('-fpreserve-control-flow'))),
        X('target',
          model='gnatcov-run',
          category='_GNATcov_',
          name='Run under GNATcov',
          menu=BINARY_TRACES_MENU + '/Run/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('run'),
                  X('arg').children('-P%PP'),
                  X('arg').children('--recursive'),
                  X('arg').children('%target'),
                  X('arg').children('-c'),
                  X('arg').children("%attr(ide_coverage'level_coverage,stmt)"),
                  X('arg').children('-o'),
                  X('arg').children('%O%T.trace'),
                  X('arg').children('%E'),
                  X('arg').children("%attr(ide_coverage'switches_run)"),
                  X('arg').children('%X'),
              ),
          ),
        X('target',
          model='gnatcov-coverage',
          category='_GNATcov_',
          name='Generate GNATcov Main Report',
          menu=BINARY_TRACES_MENU + '/Generate Report/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('coverage'),
                  X('arg').children('-P%PP'),
                  X('arg').children('--recursive'),
                  X('arg').children('%target'),
                  X('arg').children('-c'),
                  X('arg').children("%attr(ide_coverage'level_coverage,stmt)"),
                  X('arg').children('--annotate=xcov+'),
                  X('arg').children('--output-dir=%O'),
                  X('arg').children('-T'),
                  X('arg').children('%O%T.trace'),
                  X('arg').children("%attr(ide_coverage'switches_coverage)"),
                  X('arg').children('%X'),
              ),
          ),
    ]

    SOURCE_TRACES_BUILD_TARGETS = [
        X('target',
          model='gnatcov-run',
          category='_GNATcov_',
          name='Run GNATcov with instrumentation',
          menu=SOURCE_TRACES_MENU + '/Instrumentation/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('instrument'),
                  X('arg').children('-P%PP'),
                  X('arg').children('%subdirsarg'),
                  X('arg').children('--level'),
                  X('arg').children("%attr(ide_coverage'level_run,stmt)"),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_dump_trigger_arg())'),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_dump_channel_arg())'),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_dump_filename_simple_arg())'),
                  X('arg').children('%X'),
              ),
          ),
        X('target',
          model='gnatcov-build-main',
          category='_GNATcov_',
          name='GNATcov Build Coverage Runtime',
          menu=SOURCE_TRACES_MENU + '/Build/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('FALSE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('%builder'),
                  X('arg').children('%X'),
                  X('arg').children('-f'),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_rts_arg())'),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_coverage_runtime_project_arg())'),
                  X('arg').children(
                      '%python' +
                      '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'),
              )),
        X('target',
          model='gnatcov-build-main',
          category='_GNATcov_',
          name='GNATcov Install Coverage Runtime').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('FALSE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gprinstall'),
                  X('arg').children('%X'),
                  X('arg').children('%target'),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_rts_arg())'),
                  X('arg').children('-f'),
                  X('arg').children('-p'),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_coverage_runtime_project_arg())'),
                  X('arg').children(
                      '%python' +
                      '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'),
                  X('arg').children(
                      '%python' +
                      '(gnatcov.GNATcovPlugin.get_prefix_arg())'))),
        X('target',
          model='gnatcov-build-main',
          category='_GNATcov_',
          name='GNATcov Build Instrumented Main',
          menu=SOURCE_TRACES_MENU + '/Build/').children(
              X('target-type').children('executable'),
              X('in-toolbar').children('FALSE'),
              X('in-menu').children('TRUE'),
              X('read-only').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('%builder'),
                  X('arg').children('%X'),
                  X('arg').children('-p'),
                  X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' +
                                    'get_rts_arg())'),
                  X('arg').children('-P%PP'),
                  X('arg').children('%subdirsarg'),
                  X('arg').children('--src-subdirs=gnatcov-instr'),
                  X('arg').children(
                      '%python' + '(gnatcov.GNATcovPlugin.' +
                      'get_installed_coverage_runtime_project_arg())'))),
        X('target',
          model='gnatcov-coverage',
          category='_GNATcov_',
          name='Generate GNATcov Instrumented Main Report',
          menu=SOURCE_TRACES_MENU + '/Generate Report/').children(
              X('target-type').children('executable'),
              X('read-only').children('TRUE'),
              X('in-menu').children('TRUE'),
              X('output-parsers').children(
                  'output_chopper utf_converter console_writer end_of_build'),
              X('iconname').children('gps-build-all-symbolic'),
              X('launch-mode').children('MANUALLY'),
              X('command-line').children(
                  X('arg').children('gnatcov'),
                  X('arg').children('coverage'),
                  X('arg').children('-P%PP'),
                  X('arg').children('%subdirsarg'),
                  X('arg').children('%target'),
                  X('arg').children('-c'),
                  X('arg').children("%attr(ide_coverage'level_coverage,stmt)"),
                  X('arg').children('--annotate=xcov+'),
                  X('arg').children('--output-dir=%O'),
                  X('arg').children('-T'),
                  X('arg').children('%O%T.srctrace'),
                  X('arg').children("%attr(ide_coverage'switches_coverage)"),
                  X('arg').children('%X'),
              ),
          ),
    ]
    GNATCOV_DOCUMENTATION = [
        X('doc_path').children(gnatcov_doc_path),
        X('documentation_file').children(
            X('name').children(gnatcov_doc_index),
            X('descr').children("GNATcoverage User's Guide"),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                "/Help/GNATcoverage/GNATcoverage User's Guide"),
        ),
    ]

    GNATEMU_DOCUMENTATION = [
        X('doc_path').children('share/doc/gnatemu/html'),
        X('documentation_file').children(
            X('name').children('gnatemulator.html'),
            X('descr').children('GNATemulator Documentation'),
            X('category').children('GNATcoverage'),
            X('menu', before='About').children(
                '/Help/GNATcoverage/GNATemulator Documentation'),
        ),
    ]

    __run_gnatcov_wf_build_target = None
    # The 'Run GNATcoverage' workflow Build Target

    __run_gnatcov_instr_wf_build_target = None

    # The 'Run GNATcoverage with instrumentation' workflow Build Target

    def setup(self):
        # This plugin makes sense only if GNATcoverage is available.
        if not self.is_gnatcov_available():
            return

        # Create all custom things that do not require GPS' GUI to be ready
        # (i.e.: all but menus and hooks).
        for xml_nodes in (
                self.BUILD_MODES,
                self.GNATCOV_DOCUMENTATION,
                self.GNATEMU_DOCUMENTATION,
        ):
            GPS.parse_xml(list_to_xml(xml_nodes))

        # Update the GNATcoverage workflow Build Targets, creating them and
        # showing/hiding them appropriately. Also fill the custom targets.
        process = GPS.Process(["gnatcov", "--help"])
        help_msg = process.get_result()
        GPS.parse_xml(
            list_to_xml(self.BUILD_TARGET_MODELS).format(help=help_msg))
        self.update_worflow_build_targets()

        # Try to retrieve a prebuilt GNATcov runtime from the history
        global prebuilt_runtime_path
        prebuilt_runtime_path = GPS.History.get(RUNTIME_PATH_HIST_KEY,
                                                most_recent=True)

        GPS.Hook('compilation_finished').add(self.on_compilation_finished)

    def teardown(self):
        GNATcovPlugin.remove_secure_temp_dir()

    def project_view_changed(self):
        if self.is_gnatcov_available():
            self.update_worflow_build_targets()

    def update_worflow_build_targets(self):
        gnatcov_available = self.is_gnatcov_available()
        instrumentation_supported = self.is_instrumentation_supported()

        if gnatcov_available and not self.__run_gnatcov_wf_build_target:
            if self.is_binary_supported():
                workflows.create_target_from_workflow(
                    target_name="Run GNATcoverage",
                    workflow_name="run-gnatcov",
                    workflow=self.run_gnatcov_wf,
                    in_toolbar=not instrumentation_supported,
                    icon_name="gps-run-gnatcov-symbolic",
                    parent_menu=BINARY_TRACES_MENU + "/Run All Actions/")

                self.__run_gnatcov_wf_build_target = \
                    GPS.BuildTarget("Run GNATcoverage")

                GPS.parse_xml(list_to_xml(self.BINARY_TRACES_BUILD_TARGETS))

            if instrumentation_supported:
                workflows.create_target_from_workflow(
                    target_name="Run GNATcoverage with instrumentation",
                    workflow_name="run-gnatcov-with-instrumentation",
                    in_toolbar=True,
                    icon_name="gps-run-gnatcov-symbolic",
                    workflow=self.run_gnatcov_with_instrumentation_wf,
                    parent_menu=SOURCE_TRACES_MENU + "/Run All Actions/")

                self.__run_gnatcov_instr_wf_build_target = \
                    GPS.BuildTarget("Run GNATcoverage with instrumentation")

                # We want the toolbar to be ordered according to the coverage
                # process:
                # GNATcoverage source traces
                #  -> Instrument
                #  -> Build
                #  -> Run
                #  -> Generate Report
                #
                # As the Run action is a a workflow, and is not included in
                # SOURCE_TRACES_BUILD_TARGETS, we have to incorporate it in the
                # middle. We thus first parse the XML corresponding to the
                # Instrument and Build steps, then parse the XML for the Run
                # workflow, and in the end, for the Generate Report build
                # target.

                # Instrument and Build

                GPS.parse_xml(list_to_xml(
                    self.SOURCE_TRACES_BUILD_TARGETS[:4]))

                workflows.create_target_from_workflow(
                    target_name="Run instrumented main",
                    workflow_name="run-instrumented-main",
                    in_toolbar=False,
                    icon_name="gps-run-gnatcov-symbolic",
                    workflow=self.run_instrumented_main_wf,
                    parent_menu=SOURCE_TRACES_MENU + "/Run/")

                # Generate Report

                GPS.parse_xml(list_to_xml(
                    self.SOURCE_TRACES_BUILD_TARGETS[4:]))

        if not gnatcov_available:
            if self.__run_gnatcov_wf_build_target:
                self.__run_gnatcov_wf_build_target.hide()

            if self.__run_gnatcov_instr_wf_build_target:
                self.__run_gnatcov_instr_wf_build_target.hide()

        elif not instrumentation_supported:
            if self.__run_gnatcov_instr_wf_build_target:
                self.__run_gnatcov_instr_wf_build_target.hide()

    def is_gnatcov_available(self):
        return os_utils.locate_exec_on_path('gnatcov') != ""

    # Return the tool version as (major version, minor version)
    def version(self, exe):
        latest_version = (23, 0)
        version_out = GPS.Process(exe + " --version").get_result()

        # Support a gnatcov built in dev mode
        if version_out == "GNATcoverage development-tree":
            return latest_version

        matches = TOOL_VERSION_REGEXP.findall(version_out.splitlines()[0])
        return matches[0]

    def is_binary_supported(self):
        # Starting from GNATcov version 22.0, binary traces are no longer
        # supported in native.

        try:
            version_major, _ = self.version("gnatcov")
        except Exception:
            # Can happen with the GS testuite if we use a fake gnatcov exe
            return True

        return GPS.get_target() != "" or int(version_major) < 22

    def is_instrumentation_supported(self):
        # Check if GNATcov and GPRbuild are recent enough (after the 21
        # release)
        for exe in 'gnatcov', 'gprbuild':
            try:
                version_major, version_minor = self.version(exe)
            except Exception as e:
                # Can happen with the GS testuite if we use a fake gnatcov exe
                GPS.Console().write(str(e))
                return False

            if not (int(version_major) >= 22 or
                    (int(version_major) == 21 and version_minor[-1] != "w")):
                GPS.Logger("GNATCOVERAGE").log("instrumentation mode not " +
                                               "supported due to an older %s" %
                                               exe)
                return False

        return True

    def run_gnatcov_wf(self, main_name):
        # Build the project with GNATcov switches

        p = promises.TargetWrapper("GNATcov Build Main")
        r = yield p.wait_on_execute()
        if r != 0:
            GPS.Console("Messages").write("Can't build the project with " +
                                          "the GNATcov switches",
                                          mode="error")
            return

        # Get the executable to analyze
        exe = str(GPS.File(main_name).executable_path)

        # Run GNATcov on it
        p = promises.TargetWrapper("Run under GNATcov")
        r = yield p.wait_on_execute(exe)
        if r != 0:
            GPS.Console("Messages").write("GNATcov run failed ", mode="error")
            return

        # Generate and display the GNATcov Coverage Report
        p = promises.TargetWrapper("Generate GNATcov Main Report")
        r = yield p.wait_on_execute(exe)

    @workflows.run_as_workflow
    def run_instrumented_main_wf(self, main_name, generate=False):
        exe = str(GPS.File(main_name).executable_path)
        # Go to the object directory before executing the instrumented main: we
        # want to produce the trace file in the object dir and not in the
        # project's root directory
        obj_dir = GPS.Project.root().object_dirs()[0]
        GPS.cd(obj_dir)

        # Clean the previous trace file if it exists (the run can fails and
        # then the trace file will not be overwritten: it will show outdated
        # data)
        srctrace_filename = os.path.join(obj_dir, exe + ".srctrace")
        try:
            os.remove(srctrace_filename)
        except FileNotFoundError:
            pass

        # Run the instrumented main (through GNATemulator for cross targets)
        # it will generate the new trace file.
        target = GPS.get_target()
        if target == "":
            cmdargs = [exe]
            p = promises.ProcessWrapper(cmdargs)

            GPS.Console().write(' '.join(cmdargs))
            status, output = yield p.wait_until_terminate(show_if_error=True)
            if status != 0:
                GPS.Console("Messages").write(
                    "Failed to execute main with status " + str(status))
        else:
            # Launch the instrumented executable through GNATemulator
            cmdargs = GPS.BuildTarget(
                "Run GNATemulator").get_expanded_command_line()
            cmdargs.append(exe)
            GPS.Console().write(' '.join(cmdargs) + "\n")
            gnatemu_promise = promises.ProcessWrapper(cmdargs=cmdargs)
            status, output = yield gnatemu_promise.wait_until_terminate(
                show_if_error=True)

            # Put the output in a file and use 'gnatcov extract-base64-trace'
            # to retrieve the traces information from it
            out_filename = os.path.join(obj_dir, exe + ".out")

            with open(out_filename, "w") as f:
                f.write(output)
            extract_trace_cmd = [
                "gnatcov", "extract-base64-trace", out_filename,
                srctrace_filename
            ]
            GPS.Console().write(' '.join(extract_trace_cmd) + "\n")
            status = GPS.Process(extract_trace_cmd).wait()

            if status != 0:
                GPS.Console().write(
                    "Could not extract traces info from executable's output",
                    mode="error")

        if status == 0 and generate:
            # Generate and display the GNATcov Coverage Report
            p = promises.TargetWrapper(
                "Generate GNATcov Instrumented Main Report")
            yield p.wait_on_execute(exe, quiet=True)

        return status

    def run_gnatcov_with_instrumentation_wf(self, main_name):
        # Get the executable to analyze
        exe = str(GPS.File(main_name).executable_path)

        # Don't build/install the GNATcov runtime if a prebuilt one has been
        # specified.
        if not prebuilt_runtime_path:
            # Build the coverage runtime
            p = promises.TargetWrapper("GNATcov Build Coverage Runtime")
            r = yield p.wait_on_execute(quiet=True)
            if r != 0:
                GPS.Console("Messages").write("GNATcov runtime build failed ",
                                              mode="error")
                return

            # Install the coverage runtime
            p = promises.TargetWrapper("GNATcov Install Coverage Runtime")
            r = yield p.wait_on_execute(quiet=True)
            if r != 0:
                GPS.Console("Messages").write("GNATcov runtime build failed ",
                                              mode="error")
                return
        else:
            GPS.Console().write("\nPrebuilt runtime is used: %s\n" %
                                prebuilt_runtime_path)

        # Run GNATcov with instrumentation on it
        p = promises.TargetWrapper("Run GNATcov with instrumentation")
        r = yield p.wait_on_execute(exe, quiet=True)
        if r != 0:
            GPS.Console("Messages").write("GNATcov instrumentation failed ",
                                          mode="error")
            return

        # Build the instrumented main
        p = promises.TargetWrapper("GNATcov Build Instrumented Main")
        r = yield p.wait_on_execute(quiet=True)
        if r != 0:
            GPS.Console("Messages").write("Can't build the project with " +
                                          "the GNATcov switches",
                                          mode="error")
            return

        self.run_instrumented_main_wf(main_name, True)

    def reload_gnatcov_data(self):
        """Clean the coverage report and reload it from the files."""

        # If needed, switch to GNATcov build mode.
        pref_name = ("Coverage-Toolchain" if "ENABLE_GCOV" in os.environ else
                     "Coverage-Toolchain-Internal")

        if GPS.Preference(pref_name).get() != 'Gnatcov':
            GPS.Preference(pref_name).set('Gnatcov')

        GPS.execute_action("coverage clear from memory")

        if GPS.Project.root().is_harness_project():
            a = GPS.CodeAnalysis.get("Coverage Report")
            original = GPS.Project.root().original_project().file()
            a.add_gcov_project_info(original)
        else:
            GPS.execute_action("coverage load data for all projects")

    def on_compilation_finished(self,
                                hook,
                                category,
                                target_name="",
                                mode_name="",
                                status="",
                                *args):
        """Called whenever a compilation ends."""

        # If compilation failed, do nothing.
        if status:
            return

        if target_name in [
                "Generate GNATcov Main Report",
                "Generate GNATcov Instrumented Main Report"
        ]:
            self.reload_gnatcov_data()

    @staticmethod
    @interactive(category="GNATcov",
                 menu=(SOURCE_TRACES_MENU + "/Select prebuilt runtime"),
                 description=("Select the .gpr project of an " +
                              "installed  GNATcoverage prebuilt runtime."),
                 name="select gnatcov runtime")
    def set_prebuilt_runtime_action():
        """
        Create an action and a menu to be able to select an installed prebuilt
        GNATcoverage runtime.
        The path to this runtime is stored in the history, making it persistant
        accross sessions.
        """
        global prebuilt_runtime_path
        hist_path = GPS.History.get(RUNTIME_PATH_HIST_KEY, most_recent=True)

        base_dir = ""
        if hist_path:
            base_dir = os.path.dirname(hist_path)

        file = GPS.MDI.file_selector(base_dir=base_dir)
        prebuilt_runtime_path = file.path
        GPS.History.add(RUNTIME_PATH_HIST_KEY, file.path)

    @staticmethod
    def get_secure_temp_dir():
        """
        Create a secure temp directory using tempfile.mkdtemp.
        This directory should be deleted manually after usage (see the
        GNATcovPlugin.remove_secure_temp_dir function for that).
        """
        global secure_temp_dir

        if not secure_temp_dir:
            secure_temp_dir = tempfile.mkdtemp()

        return secure_temp_dir

    @staticmethod
    def remove_secure_temp_dir():
        """
        Remove the secure temp dir created via
        GNATcovPlugin.get_secure_temp_dir, if it exists.
        """
        global secure_temp_dir

        if secure_temp_dir:
            try:
                shutil.rmtree(secure_temp_dir)
            except Exception as e:
                GPS.Logger("GNATCOVERAGE").log(
                    "exception when removing temp dir: %s" % e)
            secure_temp_dir = None

    @staticmethod
    def get_coverage_runtime_gpr_name():
        """
        Return the absolute path to the gnatcov instrumentation runtime project
        to use for the current target/runtime.
        """
        runtime_attr = GPS.get_runtime()

        # Locate the directory that contains the gnatcov instrumentation
        # runtime projects.
        gnatcov_path = os_utils.locate_exec_on_path("gnatcov")
        gnatcov_dir = os.path.dirname(gnatcov_path)
        rts_dir = os.path.join(gnatcov_dir, os.pardir, "share", "gnatcoverage",
                               "gnatcov_rts")

        default = os.path.join(rts_dir, "gnatcov_rts.gpr")
        full = os.path.join(rts_dir, "gnatcov_rts_full.gpr")

        # Pick the restricted profile for BB runtimes ("default"), the "full"
        # one otherwise (unless the full one does not exist, after the
        # gnatcov_rts merge):

        if ("ravenscar" in runtime_attr or "zfp" in runtime_attr
                or "light" in runtime_attr or "embedded" in runtime_attr
                or not os.path.exists(full)):
            return default
        else:
            return full

    @staticmethod
    def get_rts_arg():
        """
        Return the needed --RTS option for gnatcov command lines.
        """
        runtime_attr = GPS.get_runtime()

        if runtime_attr == "":
            return ""
        else:
            return "--RTS=%s" % runtime_attr

    @staticmethod
    def get_coverage_runtime_project_arg():
        """
        Return the project option ("-P...") to use the gnatcov instrumentation
        runtime for the current target/runtime.
        """

        return "-P" + GNATcovPlugin.get_coverage_runtime_gpr_name()

    @staticmethod
    def get_dump_trigger_arg():
        runtime_attr = GPS.get_runtime()

        # If we have a BB runtime profile around, pick the closest
        # plausible match. Assume atexit is usable otherwise:

        if ("ravenscar" in runtime_attr or "light-tasking" in runtime_attr
                or "embedded" in runtime_attr):
            return "--dump-trigger=ravenscar-task-termination"
        elif ("zfp" in runtime_attr or "light" in runtime_attr):
            return "--dump-trigger=main-end"
        else:
            return "--dump-trigger=atexit"

    @staticmethod
    def get_dump_channel_arg():
        target_attr = GPS.get_target()

        if target_attr == "":
            return "--dump-channel=bin-file"
        else:
            return "--dump-channel=base64-stdout"

    @staticmethod
    def get_dump_filename_simple_arg():
        target_attr = GPS.get_target()

        if target_attr == "":
            return "--dump-filename-simple"
        else:
            return ""

    @staticmethod
    def get_relocate_build_tree_arg():
        return "--relocate-build-tree=" + GNATcovPlugin.get_secure_temp_dir()

    @staticmethod
    def get_prefix_arg():
        return "--prefix=" + GNATcovPlugin.get_secure_temp_dir()

    @staticmethod
    def get_installed_coverage_runtime_project_arg():
        if prebuilt_runtime_path:
            return "--implicit-with=%s" % prebuilt_runtime_path
        else:
            install_dir = os.path.join(GNATcovPlugin.get_secure_temp_dir(),
                                       "share", "gpr")
            gpr_name = os.path.basename(
                GNATcovPlugin.get_coverage_runtime_gpr_name())
            return "--implicit-with=" + os.path.join(install_dir, gpr_name)
Ejemplo n.º 5
0
secure_temp_dir = None
# The secure temp directory used to build the GNATcov runtime in
# instrumentation mode.

prebuilt_runtime_path = None
# Used to store the prebuilt GNATcov runtime path, if the user specified
# one.

PROJECT_ATTRIBUTES = [
    X(
        'project_attribute',
        package='IDE_Coverage',
        name='Gnatcov_Mode_Switches',
        label="Switches in 'gnatcov' mode",
        description=("Extra build switches to pass to the builder when in"
                     " 'gnatcov' mode."),
        editor_page='GNATcov',
        editor_section='Build',
        hide_in='wizard library_wizard',
    ).children(X('string')),
    X(
        'project_attribute',
        name='Level_Run',
        label='Coverage Level',
        package='IDE_Coverage',
        editor_page='GNATcov',
        editor_section='Run',
        hide_in='wizard library_wizard',
        description='The coverage level to pass to gnatcov run.',
    ).children(
Ejemplo n.º 6
0
class GNATfuzzPlugin(Module):
    """The main support plugin for GNATfuzz"""

    # Define some build targets
    BUILD_TARGETS = [
        X("target-model", name="gnatfuzz-generate-model",
          category="").children(
              X("description").children("Launch gnatfuzz generate"),
              X("command-line").children(),
              X("iconname").children("gps-build-all-symbolic"),
              X("switches", command="gnatfuzz", columns="2",
                lines="2").children(
                    X(
                        "field",
                        line="1",
                        column="1",
                        label="Output directory",
                        switch="-o",
                        tip="the directory in which to generate the harness",
                    )),
          ),
        X("target-model", name="gnatfuzz-fuzz-model", category="").children(
            X("description").children("Launch gnatfuzz fuzz"),
            X("command-line").children(),
            X("iconname").children("gps-build-all-symbolic"),
            X("switches", command="gnatfuzz", columns="2", lines="1").children(
                X(
                    "spin",
                    line="1",
                    column="1",
                    label="Fuzzing cores",
                    separator="=",
                    switch="--cores",
                    default="0",
                    min="0",
                    max="32",
                    tip="The number of cores to use. Use 0 for automatic.",
                ),
                X(
                    "combo",
                    line="1",
                    column="1",
                    label="AFL mode",
                    separator="=",
                    switch="--afl-mode",
                    default="PERSIST",
                    tip="The AFL run mode. See the AFL++ documentation.",
                ).children(
                    X("combo-entry", label="PLAIN", value="afl_plain"),
                    X("combo-entry", label="PERSIST", value="afl_persist"),
                    # "DEFER" and "DEFER_AND_PERSIST" are not available yet;
                    # uncomment these lines when they are available.
                    #    X("combo-entry", label="DEFER", value="afl_defer"),
                    #    X(
                    #        "combo-entry",
                    #        label="DEFER_AND_PERSIST",
                    #        value="afl_defer_and_persist",
                    #    ),
                ),
            ),
        ),
        X(
            "target",
            model="custom",
            category="_GNATfuzz_",
            name="gnatfuzz analyze project",
        ).children(
            X("target-type").children(""),
            X("in-toolbar").children("FALSE"),
            X("in-menu").children("FALSE"),
            X("read-only").children("TRUE"),
            X("output-parsers").children(
                "output_chopper utf_converter console_writer end_of_build"),
            X("iconname").children("gps-build-all-symbolic"),
            X("launch-mode").children("MANUALLY"),
            X("command-line").children(
                X("arg").children("gnatfuzz"),
                X("arg").children("analyze"),
                X("arg").children("-P%PP"),
                X("arg").children("%subdirsarg"),
                X("arg").children("%X"),
            ),
        ),
        X(
            "target",
            model="custom",
            category="_GNATfuzz_",
            name="gnatfuzz analyze file",
        ).children(
            X("target-type").children(""),
            X("in-toolbar").children("FALSE"),
            X("in-menu").children("FALSE"),
            X("read-only").children("TRUE"),
            X("output-parsers").children(
                "output_chopper utf_converter console_writer end_of_build"),
            X("iconname").children("gps-build-all-symbolic"),
            X("launch-mode").children("MANUALLY"),
            X("command-line").children(
                X("arg").children("gnatfuzz"),
                X("arg").children("analyze"),
                X("arg").children("-P%PP"),
                X("arg").children("%subdirsarg"),
                X("arg").children("%X"),
                X("arg").children("-S"),
                X("arg").children("%F"),
            ),
        ),
        X(
            "target",
            model="gnatfuzz-fuzz-model",
            category="_GNATfuzz_",
            name="gnatfuzz fuzz",
            menu="",
        ).children(
            X("target-type").children(""),
            X("in-toolbar").children("FALSE"),
            X("in-menu").children("FALSE"),
            X("read-only").children("TRUE"),
            X("output-parsers").children(
                "output_chopper utf_converter console_writer end_of_build"),
            X("iconname").children("gps-build-all-symbolic"),
            X("launch-mode").children("MANUALLY_WITH_DIALOG"),
            X("command-line").children(
                X("arg").children("gnatfuzz"),
                X("arg").children("fuzz"),
                X("arg").children("-P%PP"),
                X("arg").children("%subdirsarg"),
            ),
        ),
        X(
            "target",
            model="gnatfuzz-generate-model",
            category="_GNATfuzz_",
            name="gnatfuzz generate",
            menu="",
        ).children(
            X("target-type").children(""),
            X("in-toolbar").children("FALSE"),
            X("in-menu").children("FALSE"),
            X("read-only").children("TRUE"),
            X("output-parsers").children(
                "output_chopper utf_converter console_writer end_of_build"),
            X("iconname").children("gps-build-all-symbolic"),
            X("launch-mode").children("MANUALLY_WITH_DIALOG"),
            X("command-line").children(
                X("arg").children("gnatfuzz"),
                X("arg").children("generate"),
                X("arg").children("-P%PP"),
                X("arg").children("%subdirsarg"),
                X("arg").children("%X"),
            ),
        ),
    ]

    def setup(self):
        # This plugin makes sense only if GNATcoverage is available:
        # return immediately if not.
        if not os_utils.locate_exec_on_path("gnatfuzz"):
            return

        # These fields are set when the project is a harness project
        self.user_project = None  # The original user project
        self.output_dir = None  # The gnatfuzz output dir

        # Create the build targets
        GPS.parse_xml(list_to_xml(self.BUILD_TARGETS))

        ref_menu = "Analyze"

        # Create the actions
        make_interactive(
            self.gnatfuzz_analyze_project,
            category="GNATfuzz",
            name="gnatfuzz analyze project workflow",
            menu="/GNATfuzz/Analyze project",
            before=ref_menu,
        )
        make_interactive(
            self.gnatfuzz_analyze_file,
            category="GNATfuzz",
            name="gnatfuzz analyze file workflow",
            menu="/GNATfuzz/Analyze file",
            before=ref_menu,
        )
        make_interactive(
            self.gnatfuzz_generate,
            category="GNATfuzz",
            name="gnatfuzz generate workflow",
        )
        make_interactive(
            self.gnatfuzz_fuzz_start_stop,
            filter=self.is_harness_project,
            category="GNATfuzz",
            name="gnatfuzz fuzz workflow",
            menu="/GNATfuzz/Start\\/Stop Fuzzing Session",
            before=ref_menu,
        )
        make_interactive(
            self.switch_to_user_project,
            filter=self.is_harness_project,
            category="GNATfuzz",
            name="gnatfuzz switch to user project",
            menu="/GNATfuzz/Switch to User Project",
            before=ref_menu,
        )

        # Call the project changed hook to refresh the harness flags
        self.project_view_changed()

    def switch_to_user_project(self):
        """Switch back from the harness project to the user project"""
        if self.user_project is not None:
            GPS.Project.load(self.user_project)

    def teardown(self):
        """Inherited"""
        # Nothing to do
        pass

    def is_harness_project(self, context):
        return self.user_project is not None

    def project_view_changed(self):
        """React to a project view change"""
        project_dir = os.path.dirname(GPS.Project.root().file().name())
        config_file = os.path.join(project_dir, "fuzz_config.json")

        if os.path.exists(config_file):
            # A config file has been found: this is a harness project

            # Read the contents of the config field
            with open(config_file, "r") as f:
                decoded = json.load(f)
            self.user_project = decoded["user_project"]
            self.output_dir = decoded["output_directory"]
        else:
            # This is not a harness project
            self.user_project = None
            self.output_dir = None

    def error(self, msg):
        """Convenience function to log an error in the Messages"""
        GPS.Console("Messages").write(msg + "\n", mode="error")

    ###########
    # Analyze #
    ###########

    def on_fuzzable_subprogram_click(self, message):
        """React to a click on a "fuzzable subprogram" message box"""
        # This is akin to a compilation: save everything that needs saving
        GPS.MDI.save_all()

        output_dir = os.path.join(GPS.Project.root().object_dirs()[0],
                                  "fuzz_harness")

        if os.path.exists(output_dir):
            shutil.rmtree(output_dir)

        analyze_report_file = self.path_to_analyze_json()
        if not os.path.exists(analyze_report_file):
            self.error(f"Analyze file not found: {analyze_report_file}")
            return

        # Launch "gnatfuzz generate"
        GPS.BuildTarget("gnatfuzz generate").execute(
            extra_args=[
                "-o",
                output_dir,
                "--analysis",
                analyze_report_file,
                "--subprogram-id",
                str(message.analyze_id),
            ],
            synchronous=True,
        )
        # TODO: make this a workflow, in case this takes a long time.

        harness_project = os.path.join(output_dir, "fuzz_testing",
                                       "fuzz_test.gpr")
        if os.path.exists(harness_project):
            r = GPS.MDI.yes_no_dialog(
                "Harness generation successful.\n\nSwitch to harness project?")

            if r:
                GPS.Project.load(harness_project)

    def create_messages_from_analyze_json_entry(self, entry):
        """Create a GS Messages from one toplevel entry in "fuzzable_subprograms"
        in analyze.json
        """

        file = entry["source_filename"]
        line = entry["start_line"]
        if "id" in entry:
            # If there is an "id", this is a non-generic case

            m = GPS.Message(
                category="Fuzzable Subprograms",
                file=GPS.File(file),
                line=line,
                column=1,
                text="Fuzzable subprogram",
                show_on_editor_side=True,
                show_in_locations=True,
                auto_jump_to_first=False,
            )
            m.set_subprogram(
                self.on_fuzzable_subprogram_click,
                "gps-compile-symbolic",
                "Generate fuzz harness",
            )
            m.__setattr__("analyze_id", entry["id"])

        elif "instantiations" in entry:
            # This is a generic case: create a message with the chain
            if len(entry["instantiations"]) == 0:
                self.error("analyze.json: 'instantiations' empty")

            for sub in entry["instantiations"]:
                id = sub["id"]
                # First go through the chain to craft a label for the
                # toplevel message
                labels = []
                chain = sub["instantiation_chain"]
                if len(chain) == 0:
                    self.error("analyze.json: 'instantiation_chain' empty")

                chain.reverse()  #
                for chain_entry in chain:
                    labels.append(chain_entry["label"])
                text = "Fuzzable subprogram: " + ":".join(labels)
                m = GPS.Message(
                    category="Fuzzable Subprograms",
                    file=GPS.File(file),
                    line=line,
                    column=1,
                    text=text,
                    show_on_editor_side=True,
                    show_in_locations=True,
                    auto_jump_to_first=False,
                )
                m.set_subprogram(
                    self.on_fuzzable_subprogram_click,
                    "gps-compile-symbolic",
                    "Generate fuzz harness: " + ":".join(labels),
                )
                m.__setattr__("analyze_id", id)
                for chain_entry in chain:
                    label = chain_entry["label"]
                    chain_line = chain_entry["start_line"]
                    chain_filename = chain_entry["source_filename"]
                    base = os.path.basename(chain_filename)
                    sub_msg = f"... instantiated in {label} at {base}:{chain_line}"
                    # Create a nested message for each entry in the chain
                    m.create_nested_message(GPS.File(chain_filename),
                                            chain_line, 1, sub_msg)
        else:
            self.error("analyze.json entry without 'id' nor 'instantiations'")

    def path_to_analyze_json(self):
        """Return the path to analyze.json"""
        return os.path.join(GPS.Project.root().object_dirs()[0], "gnatfuzz",
                            "analyze.json")

    def process_analyze_messages(self):
        """Process the analyze.json file"""
        # Find the analyze report
        analyze_report_file = self.path_to_analyze_json()
        if not os.path.exists(analyze_report_file):
            self.error(f"Analyze file not found: {analyze_report_file}")
            return

        # Open the analyze report
        with open(analyze_report_file, "r") as f:
            try:
                decoded = json.load(f)
            except Exception:
                self.error(f"{analyze_report_file} corrupted")

        if "fuzzable_subprograms" not in decoded:
            self.error(f"{analyze_report_file} missing 'fuzzable_subprograms'")
            return

        # Create messages
        for entry in decoded["fuzzable_subprograms"]:
            self.create_messages_from_analyze_json_entry(entry)

    def gnatfuzz_analyze_project_workflow(self, task):
        """Workflow for 'gnatfuzz analyze project'."""

        # Launch the analyze target in the background
        p = promises.TargetWrapper("gnatfuzz analyze project")
        r = yield p.wait_on_execute()
        if r != 0:
            self.error("gnatfuzz analyze returned nonzero")
            return
        self.process_analyze_messages()

    def gnatfuzz_analyze_file_workflow(self, task):
        """Workflow for 'gnatfuzz analyze'."""

        # Launch the analyze target in the background
        p = promises.TargetWrapper("gnatfuzz analyze file")
        r = yield p.wait_on_execute()
        if r != 0:
            self.error("gnatfuzz analyze returned nonzero")
            return
        self.process_analyze_messages()

    def clear_analyze_messages(self):
        """Clear messages generated by 'gnatfuzz analyze'"""
        for m in GPS.Message.list(category="Fuzzable Subprograms"):
            m.remove()

    def gnatfuzz_analyze_project(self):
        """Action to launch the 'gnatfuzz analyze' workflow"""
        self.clear_analyze_messages()
        workflows.task_workflow("gnatfuzz analyze",
                                self.gnatfuzz_analyze_project_workflow)

    def gnatfuzz_analyze_file(self):
        self.clear_analyze_messages()
        """Action to launch the 'gnatfuzz analyze' workflow on a file"""
        workflows.task_workflow("gnatfuzz analyze",
                                self.gnatfuzz_analyze_file_workflow)

    ############
    # Generate #
    ############

    def gnatfuzz_generate_workflow(self, task):
        """The 'gnatfuzz generate' workflow"""
        p = promises.TargetWrapper("gnatfuzz generate")
        r = yield p.wait_on_execute()
        if r != 0:
            GPS.Console("Messages").write("gnatfuzz generate returned nonzero",
                                          mode="error")
            return

    def gnatfuzz_generate(self):
        """Action to launch the 'gnatfuzz generate' workflow"""
        workflows.task_workflow("gnatfuzz generate",
                                self.gnatfuzz_generate_workflow)

    ########
    # Fuzz #
    ########

    def gnatfuzz_fuzz_workflow(self, task):
        """The 'gnatfuzz fuzz' workflow"""
        # Move away the previous fuzzing session dir

        fuzz_session_dir = os.path.join(self.output_dir, "fuzz_testing",
                                        "session")
        if os.path.exists(fuzz_session_dir):
            shutil.rmtree(fuzz_session_dir)

        # Generate the -X switches
        args = []
        for variable, value in GPS.Project.scenario_variables().items():
            # We pass all -X switches except the ones that are internal
            # to gnatfuzz.
            if not (variable.startswith("GNATFUZZ") or variable == "AFL_MODE"):
                args.append(f"-X{variable}={value}")

        args.extend([
            f"--corpus-path={self.output_dir}/fuzz_testing/starting_corpus",
            f"--stop-criteria={self.output_dir}"
            "/fuzz_testing/user_configuration/stop_criteria.xml",
        ])

        GPS.BuildTarget("gnatfuzz fuzz").execute(
            extra_args=args,
            synchronous=False,
        )

        # Create a CodeAnalysis object to store the coverage data
        a = GPS.CodeAnalysis.get("gnatfuzz")

        xcov_files = {}  # Keys: full path, values: timestamp

        # Launch the GNATfuzz view
        GPS.execute_action("open GNATfuzz view")

        # Clear the GNATfuzz view
        GPS.execute_action("clear GNATfuzz view")

        # Monitor the disk for the presence of xcov files

        while True:
            # This is interrupted by the user calling the menu again,
            # in which case the Task will be removed: see at the bottom
            # of the loop.
            yield promises.timeout(FUZZ_MONITOR_TIMEOUT)

            if not os.path.exists(fuzz_session_dir):
                self.error(
                    f"fuzz session directory {fuzz_session_dir} not found")
                self.stop_fuzz()
                break

            # Monitor for coverage files
            found_xcov_files = glob.glob(
                os.path.join(fuzz_session_dir, "coverage_output", "*.xcov"))
            for xcov in found_xcov_files:
                (mode, ino, dev, nlink, uid, gid, size, atime, mtime,
                 ctime) = os.stat(xcov)
                timestamp = time.ctime(mtime)
                if xcov not in xcov_files or xcov_files[xcov] != timestamp:
                    xcov_files[xcov] = timestamp
                    base = os.path.basename(xcov)[:-5]
                    a.add_gcov_file_info(GPS.File(base),
                                         GPS.File(xcov),
                                         raise_window=False)
                    a.show_file_coverage_info(GPS.File(base))

            # Monitor for crashes
            view = get_gnatfuzz_view()
            if view is not None:
                view.refresh()

            # The end condition
            tasks = [t for t in GPS.Task.list() if t.name() == "gnatfuzz fuzz"]
            if len(tasks) == 0:
                break

        return

    def is_fuzz_running(self):
        """Return True if "gnatfuzz fuzz" is running"""
        tasks = [t for t in GPS.Task.list() if t.name() == "gnatfuzz fuzz"]
        return len(tasks) > 0

    def stop_fuzz(self):
        tasks = [t for t in GPS.Task.list() if t.name() == "gnatfuzz fuzz"]
        if len(tasks) > 0:
            tasks[0].interrupt()

    def gnatfuzz_fuzz_start_stop(self):
        """Action to start/stop the 'gnatfuzz fuzz' workflow"""

        # Check whether we have a "gnatfuzz fuzz" process running: if so,
        # interrupt it, and the workflow should terminate.
        if self.is_fuzz_running():
            self.stop_fuzz()
        else:
            workflows.task_workflow(FUZZ_TASK_NAME,
                                    self.gnatfuzz_fuzz_workflow)