Extending the Run Manager — Builder 3.38.1 documentation

# Extending the Run Manager

Running projects is managed by the `Ide.RunManager`. You can provide special “run handlers” to alter how the applications are executed.

For example, the Sysprof-based profiler uses a custom run handler to ensure that the profiler can monitor the process. Additionally, Builder’s GDB debugger backend will alter the arguments so that GDB is used to launch the program.

Generally, hooking into the run manager involves adding a menu entry to the “Run Button” and a callback to process the launch request.

Let’s take a look at a practical example using the Valgrind plugin.

Embed file as a resource matching /org/gnome/Builder/plugins/$module_name/gtk/menus.ui

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <menu id="run-menu">
        <section id="run-menu-section">
          <item>
            <attribute name="id">valgrind-run-handler</attribute>
            <attribute name="after">default-run-handler</attribute>
            <attribute name="action">run-manager.run-with-handler</attribute>
            <attribute name="target">valgrind</attribute>
            <attribute name="label" translatable="yes">Run with Valgrind</attribute>
            <attribute name="verb-icon-name">system-run-symbolic</attribute>
            <attribute name="accel">&lt;Control&gt;F10</attribute>
          </item>
        </section>
      </menu>
    </interface>

For more information on embedding resources with Python-based plugins, see creating embedded GResources.

Now register a run handler to handle the launch request.

    from gi.repository import Ide
    from gi.repository import GLib
    from gi.repository import GObject

    _ = Ide.gettext

    class ValgrindWorkbenchAddin(GObject.Object, Ide.WorkbenchAddin):
        workbench = None
        has_handler = False

        def do_load(self, workbench):
            self.workbench = workbench

            build_manager = self.workbench.get_context().get_build_manager()
            build_manager.connect('notify::pipeline', self.notify_pipeline)
            self.notify_pipeline(build_manager, None)

        def notify_pipeline(self, build_manager, pspec):
            run_manager = self.workbench.get_context().get_run_manager()

            # When the pipeline changes, we need to check to see if we can find
            # valgrind inside the runtime environment.
            pipeline = build_manager.get_pipeline()
            if pipeline:
                runtime = pipeline.get_config().get_runtime()
                if runtime and runtime.contains_program_in_path('valgrind'):
                    if not self.has_handler:
                        run_manager.add_handler('valgrind', _('Run with Valgrind'), 'system-run-symbolic', '<primary>F10', self.valgrind_handler)
                        self.has_handler = True
                    return

            if self.has_handler:
                run_manager.remove_handler('valgrind')

        def do_unload(self, workbench):
            if self.has_handler:
                run_manager = self.workbench.get_context().get_run_manager()
                run_manager.remove_handler('valgrind')
            self.workbench = None

        def valgrind_handler(self, run_manager, runner):
            # We want to run with valgrind --log-fd=N so that we get the valgrind
            # output redirected to our temp file. Then when the process exits, we
            # we will open the temp file in the builder editor.
            source_fd, name = GLib.file_open_tmp('gnome-builder-valgrind-XXXXXX.txt')
            map_fd = runner.take_fd(source_fd, -1)
            runner.prepend_argv('--track-origins=yes')
            runner.prepend_argv('--log-fd='+str(map_fd))
            runner.prepend_argv('valgrind')
            runner.connect('exited', self.runner_exited, name)

        def runner_exited(self, runner, name):
            # If we weren't unloaded in the meantime, we can open the file using
            # the "editor" hint to ensure the editor opens the file.
            if self.workbench:
                uri = Ide.Uri.new('file://'+name, 0)
                self.workbench.open_uri_async(uri, 'editor', 0, None, None, None)