Improving the Container Workflow

As I mentioned in my talk at Scale 17x, if you aren’t using containers for building your application yet, it’s likely you will in the not-so-distant future. Moving towards an immutable base OS is a very likely future because the security advantages are so compelling. With that, comes a need for a mutable playground, and containers generally fit the bill. This is something I saw coming when I started making Builder so we have had a number of container abstractions for some time.

With growing distributions, like Fedora’s freshly released Silverblue, it becomes even more necessary to push those container boundaries soon.

This week I started playing with some new ideas for a terminal workspace in Builder. The goal is to be a bit of a swiss-army knife for container oriented development. I think there is a lot we can offer by building on the rest of Builder, even if you’re primary programming workhorse is a terminal and the IDE experience is not for you. Plumbing is plumbing is plumbing.

I’m interested in getting feedback on how developers are using containers to do their development. If that is something that you’re interested in sharing with me, send me an email (details here) that is as concise as possible. It will help me find the common denominators for good abstractions.

What I find neat, is that using the abstractions in Builder, you can make a container-focused terminal in a couple of hours of tinkering.

I also started looking into Vagrant integration and now the basics work. But we’ll have to introduce a few hooks into the build pipeline based on how projects want to be compiled. In most cases, it seems the VMs are used to push the app (and less about compiling) with dynamic languages, but I have no idea how pervasive that is. I’m curious how projects that are compiling in the VM/container deal with synchronizing code.

Another refactor that needs to be done is to give plugins insight into whether or not they can pass file-descriptors to the target. Passing a FD over SSH is not possible (although in some cases can be emulated) so plugins like gdb will have to come up with alternate mechanisms in that scenario.

I will say that trying out vagrant has been a fairly disappointing experience compared to our Flatpak workflow in Builder. The number of ways it can break is underwhelming and the performance reminds me of my days working on virtualization technology. It makes me feel even more confident in the Flatpak architecture for desktop developer tooling.

Flatpaking Terminals

One thing Builder has done for a long time is make terminals work seamlessly even if distributed using container technologies. Because pseudo-terminals are steeped in esoteric UNIX history, it can be non-obvious how to make this work.

I’m in a place to help you not have to deal with that pain because I’ve already gone through it. So I created some utility code and a demo application that can be packaged with Flatpak. If it detects it’s running under Flatpak it will use a few techniques to get a user-preferred shell executed on the host with a PTY controlled by application.

Check out the code.

Edit: The flatterm repository has been updated to use the brand new VTE_PTY_NO_CTTY flag that was added in response to this blog post. Users of Vte from git (what will be 0.58) get to enjoy writing even less code.

Podman Support in Builder

For years now, Builder has had rich abstractions for containers built right into the core of the IDE. Builder even knows the difference between build and runtime containers which naturally maps with Flatpak SDKs like org.gnome.Sdk vs org.gnome.Platform.

With the advent of operating systems focused on immutability, like Fedora Silverblue, developers are going to be increasingly developing in containers.

The technology underlying projects like Toolbox is podman. It provides a command-line tool to manage containers without a daemon by using the various container APIs afforded to us in modern Linux.

Bridging Builder’s container APIs to support podman was pretty painless on my part. A couple hours to choose the right abstractions and implementing them led me to a missing piece in podman; passing FDs to the container.

The reason that Builder requires this is that we often need to communicate across containers. An easy way to do that is over a pair of pipe() since it is anonymous. By anonymous, I mean we don’t need to share any file-system hierarchy, IPC or network namespaces, or even PTY namespace.

The most important piece that requires this in Builder is our GDB-based debugger. We use GDB inside the container so it has native access to things like build sources, libraries, symbols, and more. This is all orchestrated using GDB’s mi2 interface over a PTY, with a second PTY for the target process. When GDB lands on a breakpoint, we know how to translate paths between Builder’s container (usually Flatpak) and the target container (in this case, podman). Doing so ensures that we open the right file and line number to the user. Fundamentals, of course.

So a couple weeks later and podman exec has gained the --preserve-fds=N option, available on Rawhide and Fedora 30 (currently in updates-testing). If you have all the necessary bits in place, Builder will allow you to select a podman container from Build Preferences, and you’re off to the races.

Furthermore, you can even seamlessly get a terminal in the build environment with Control+Alt+Shift+T which can prove useful if you have to install dependencies.

Since we don’t know much about the container, we don’t have the ability to install dependencies on your behalf. But if someone were to work on Dockerfile support, I don’t see that as an intractable problem.

Here is a quick test command-line program debugging in Builder using the GDB backend to prove it all works.

Designing for Sandboxes

One of the things I talked about in my talk at Scale 17x is that there are a number of platform features coming that are enevitable.

One of those is application sandboxing.

But not every component within an application is created equal or deserves equal access to user data and system configuration. Building the next big application is increasingly requiring thinking about how you segment applications into security domains.

Given the constraints of our current operating systems, that generally means processes. Google’s Chrome was one of the first major applications to do this. The Chrome team had created a series of processes focused on different features. Each of those processes had capabilities removed (such as network, or GPU access) from the process space to reduce the damage of an attack.

Recently Google released sandboxed-api, which is an interesting idea around automatically sandboxing libraries on Linux. While interesting, limiting myself to designs that are Linux only is not currently realistic for my projects.

Since I happen to work on an IDE, one of the technologies I’ve had to become familiar with is Microsoft’s Language Server Protocol. It’s a design for worker processes to provide language-specific features.

It usually works like this:

  • Spawn a worker process, with a set of pipe()s for stdin/stdout you control
  • Use JSONRPC over the pipe()s with some well-formatted JSON commands

This design can be good for sandboxing because it allows you to spawn subprocesses that have reduced system capabilities, easily clean up after them, and provides an IPC format. Despite having written jsonrpc-glib and a number of helpers to make writing JSON from C rather clean, I’m still unhappy with it for a number of reasons. Those reasons range from everything from performance to correctness to brittleness of nonconforming implementations.

I’d like to use this design in more than just Builder but those applications are more demanding. They require passing FDs across the process boundary. (Also I’m sick of hand writing JSON RPCs and I don’t want to do that anymore).

Thankfully, we’ve had this great RPC system for years that fits the bill if you reuse the serialization format: DBus.

  • No ties to a DBus daemon
  • GDBus in GLib has a full implementation that plays well with async/sync code
  • gdbus-codegen can generate our RPC stubs and proxies
  • Well defined interfaces in XML files
  • Generated code does type enforcement to ensure contracts
  • We can easily pass FDs across the process boundary, useful for memfd/tmpfs/shm

To setup the sandboxes, we can use tools like flatpak-spawn or bwrap on Linux to restrict process capabilities before launching the target process. Stdin/stdout is left untouched so that we can communicate with the subprocess even after capabilities are dropped.

Before I (re)settled on DBus, I tried a number of other prototypes. That included writing an interface language/codegen for JSONRPC, using libvarlink, Thrift’s c_glib compiler and protobufs. I’m actually surprised I was happiest with the DBus implementation, but that’s how it goes sometimes.

While I don’t expect a lot of sandboxing around our Git support in Builder, I did use it as an opportunity to prototype what this multi-process design looks like. If you’re interested in checking it out, you can find the worker sources here.

What excites me about the future is how this type of design could be used to sandbox image loaders like GdkPixbuf. One could quite trivially have an RPC that passes a sealed memfd for compressed image contents and returns a memfd for the decoded framebuffer or pre-compressed GPU textures. Keep that process around a little while to avoid fork()/exec() overhead, and we gain a bit of robustness with very little performance drawbacks.

A Better Builder – Part I

As I mentioned in my overview of the upcoming Builder release, a lot of this development cycle focused on improving machinery the user does not see. In the past four years of development, a number of patterns emerged. When I started this project, I had some ideas of how to keep things organized, but Builder quickly grew beyond my ability to keep the whole design in my head at once.

Thankfully, I’ve worked on many large software teams during my tenure at companies like VMware and MongoDB. These company’s products have something in common, in that they’re a number of sub-systems performing specialized tasks that coordinate with each other. Not surprising, I know. But sometimes, knowing where to draw the lines between sub-systems is what differentiates products. (Interestingly, if you want to build a database engine, you better have a good reason to deviate from page 144 of Architecture of a Database System).

Now days, Builder segments the code into a series of static libraries. We have a core library that provides the basic functionality used by all sub-systems. We have a process and threading library. One library deals with representing and manipulating source code. Yet another focuses on building, executing, testing, and packaging software. To enable Language Servers, we have one too. Even the gui and editor are somewhat compartmentalized.

In the end, we link all of these static libraries into the final executable, which is then processed to generate GObject Introspection data; the *.gir and *.typelib. That means that our plugins, written in C, C++, Python, or Vala, do not need to rely on linking against any shared libraries. All symbols come from the executable with -export-dynamic. (One side-effect of this, is that we no longer allow linking our Vala based plugin into the executable, since it relies on *.gir generation).

To keep startup fast, all of our bundled C-based plugins are compiled into the final executable. That reduces the number of link loader work, file-system directory scanning and iops, and allows for more aggressive compiler optimizations. We also avoid loading some plugins in the early initialization phase, instead deferring that work until later.

The biggest change in the refactoring is IdeObject. This object has become somewhat like a multi-threaded version of what GtkObject used to be. It allows us to build a top-down object tree. The root of the tree is a specialized form, IdeContext. Each sub-system mounts itself on this tree. Plugins may have or work with objects that are descendants of the sub-systems. In the end, it makes things much easier for me to debug at runtime because I can get a clearer picture of how things interact and what plugins are active. One interesting feature of IdeObject is that we guarantee they are only ever finalized in the main thread.

The sub-system that I think resulted in the most cleanup was libide-foundry.a. This contains the build manager, pipelines, and extension points for plugins to provide the the abstraction glue. It also contains runtime abstractions, execution management, testing infrastructure, device management (phone, tablet, simulator), and toolchains. Currently, the debugger engine is a separate library, but I may fold it into this library for completeness.

When cleaning up libide-code.a, I simplified the buffer management. This code was a bit temperamental because I put a few too many features into the base class. I think that happened simply because I couldn’t yet foresee many of the ways it would be extended. Seeing how the code evolved over time allowed me to create better abstractions and extract those features.

What kicked off this whole process was an hour hack to add multi-monitor support. That somehow turned into a 2 month project that involved me ripping everything apart and putting it back together because of our initial design. I’m happy to say that I think things turned out quite well and we are poised to keep moving fast for the next few development cycles to come.

We’re getting many more requests to support external plugins in Builder. I’ve always taken the Linux kernel approach here in that I very much want to encourage upstream contributions over external plugins. However, I am starting to loosen up just a bit and we’ve added the X-Builder-ABI=3.31 key to plugin definitions. This allows us to continue to evolve our APIs but promise to not break them in micro releases. My expectation, however, is that this major refactoring will set us down the path towards some amount of ABI stability.

In my next post I’d like to cover how the UI components are abstracted and give you an idea of which plugin interfaces to use and when. In the mean time, I have plenty of documentation to write.

Builder 3.32 Sightings

We just landed the largest refactor to Builder since it’s inception. Somewhere around 100,000 lines of code where touched which is substantial for a single development cycle. I wrote a few tools to help us do that work, because that’s really the only way to do such a large refactor.

Not only does the refactor make things easier for us to maintain but it will make things easier for contributors to write new plugins. In a future blog post I’ll cover some of the new design that makes it possible.

Let’s take a look at some of the changes in Builder for 3.32 as users will see them.

First we have the greeter. It looks similar as before, although with a design refresh. But from a code standpoint, it no longer shares it’s windowing with the project workspace. Taking this approach allowed us to simplify Builder’s code and allows for a new feature you’ll see later.

Builder now gives some feedback about what files were removed when cleaning up old projects.

Builder gained support for more command-line options which can prove useful in simplifying your applications setup procedure. For example, you can run gnome-builder --clone https://gitlab.gnome.org/GNOME/gnome-builder.git to be taken directly to the clone dialog for a given URL.

The clone activity provides various messaging in case you need to debug some issues during the transfer. I may hide this behind a revealer by default, I haven’t decided yet.

Creating a new project allows specifying an application-id, which is good form for desktop applications.

We also moved the “Continue” button out of the header bar and placed it alongside content since a number of users had difficulty there.

The “omni-bar” (center of header bar) has gained support for moving through notifications when multiple are present. It can also display buttons and operational progress for rich notifications.

Completion hasn’t changed much since last cycle. Still there, still works.

Notifications that support progress can also be viewed from our progress popover similar to Nautilus and Epiphany. Getting that circle-pause-button aligned correctly was far more troublesome than you’d imagine.

The command-bar has been extracted from the bottom of the screen into a more prominent position. I do expect some iteration on design over the next cycle. I’ve also considered merging it into the global search, but I’m still undecided.

Also on display is the new project-less mode. If you open Builder for a specific file via Nautilus or gnome-builder foo.c you’ll get this mode. It doesn’t have access to the foundry, however. (The foundry contains build management and other project-based features).

The refactoring not only allowed for project-less mode but also basic multi-monitor support. You can now open a new workspace window and place it on another monitor. This can be helpful for headers, documentation, or other references.

The project tree has support for unit tests and build targets in addition to files.

Build Preferences has been rebuilt to allow plugins to extend the view. That means we’ll be able to add features like toggle buttons for meson_options.txt or toggling various clang/gcc sanitizers from the Meson plugin.

The debugger has gone through a number of improvements for resilience with modern gdb.

When Builder is full-screen, the header bar slides in more reliably now thanks to a fix I merged in gtk-3-24.

As previewed earlier in the cycle, we have rudimentary glade integration.

Also displayed here, you can select a Build Target from the project tree and run it using a registered IdeRunHandler.

Files with diagnostics registered can have that information displayed in the project tree.

The document preferences have been simplified and extracted from the side panel.

The terminal now can highlight filename:line:column patterns and allow you to ctrl+click to open just like URLs.


In a future post, we’ll cover some of what went into the refactoring. I’d like to discuss how the source tree is organized into a series of static libraries and how internal plugins are used to bridge subsystems to avoid layering violations. We also have a number of simplified interfaces for plugin authors and are beginning to have a story around ABI promises to allow for external plugins.

If you just can’t wait, you can play around with it now (and report bugs).

flatpak install https://gitlab.gnome.org/GNOME/gnome-apps-nightly/raw/master/gnome-builder.flatpakref

Until next time, Happy Hacking!

Glade Support for Builder

One of the things we’ve wanted in Builder for a while is a designer. We’ve had various prototypes in the past to see how things would have worked out, and mostly just punted on the idea because it seemed like Glade served users better than we would be able to directly.

Last week, Juan Pablo, Matthias Clasen and I met up in San Francisco to see what we could do in the short term. We discussed a couple of options that we have going forward.

  • Integrate glade 3 into Builder using libgladeui.
  • Integrate glade 3 using the external Glade application and use D-Bus to inter-operate.

Like all projects, we have some constraints.

  • Gtk 4 is in progress, and our hope is that most new application development moves towards that because the benefits are outstanding. That means the value of a Gtk 3 designer is depreciating.
  • Gtk 4 changes many fundamental designs behind the scenes. While much effort has been done to reduce the friction in porting applications, porting an UI designer is no trivial task as they necessarily reach into library internals. It is likely Gtk 4 will require creating a new designer from the ground up. Doing this as part of Gtk itself is probably worthwhile.
  • We want the designer to know about all of your .ui files so that it is easier to see widgets created using composition.
  • Allow generating signal callbacks into your existing code-base in a variety of languages.

With that in mind, I want to get the maximal benefit with the least amount of time to ship. I made a new plugin for Builder last week that gets us moving in that direction. It still needs more work to integrate with signal editing, templates, and other more advanced Glade features.

Hopefully that happens soon because I know we’ve all been waiting for it. Get it now with the Builder Nightly flatpak.

flatpak install --from https://git.gnome.org/browse/gnome-apps-nightly/plain/gnome-builder.flatpakref

Screenshot of Builder with Glade Integration

Removing my favorite feature

Years ago, when starting the Builder project, I added a real-time CPU graph so that it was easy to detect slow-downs quickly while hacking on the product. If I ever saw a main-loop stall, it would be immediately obvious.

Over the years I added more advanced features like a Sysprof-based profiler. That duplicated the feature and requires significantly less overhead. You also have the added benefit of zooming and panning.

So in a decision that was long overdue, I’m removing the real-time graph from Builder 3.32. I never did a great job of porting that code to optimal Wayland use anyway. It was really designed with Xrender/Xshm in mind where XCopyArea() was cheap and done on the GPU.

There are better solutions now anyway.