Vivienne's Blog

Thoughts about Multimedia, WebKit and GStreamer

How I set up my IDE for the WebKit container SDK

This is based on this previous blog post by Alicia, and I recommend taking a look - many things mentioned in it are still useful here.
Example of symbol documentation in Helix. The screenshot shows the entire window, with a outlined popup below the cursor, showing documentation. Reading a symbol’s documentation in a popup

The most straight-forward option would have been to just install another instance of my IDE inside the container. However, I use NixOS + Home Manager to manage and configure my packages declaratively, so the Ubuntu-based container environment would be a quite frustrating difference:

  1. Package versions will be lagging behind, and sooner or later I will have to deal with differences with configuration, features, or bugs. For example, at time of writing, neovim is packaged in Debian 24.10 at version 0.9.5, while nixpkgs ships 0.10.2. (To be fair, Flathub and Snapcraft would be up-to-date as well, but I have my gripes with those too.)

  2. Either way, I now have a new set of configurations to manage and keep in sync with their canonical versions on the host system.

  3. Any other tools I don’t install in the container, I won’t have access to - for example, for running commands from inside my IDE.

Overall, this will waste time and disk space better used for other things. So, after trying out a few different approaches, a clangd wrapper script that bridges the disconnect between my host system and the container was the first satisfying solution I found.

Conveniently, this fits well with my approach of writing wrappers around wkdev scripts to expose as much functionality as possible to my host system, to avoid manually entering the container - in effect abstracting it out of sight.

Step 1: Wrapper script #

This is roughly the script I currently use. I personally prefer nushell, but I will go into details below so you can write your own version in whatever language you prefer.

The idea is to start clangd inside the container, and use socat to expose its stdin/out to the IDE over TCP. That is to avoid this podman issue I ran into if I tried using stdin.

#!/usr/bin/env -S nu --stdin

def main [
  --name (-n): string = "wkdev-sdk"
  --show-config
] {
  # picking a random port for the connection avoids colliding with itself in case an earlier instance of this script is still around
  let port = random int 2000..5000 
  let workdir = $"/host(pwd)"

  # the container SDK mounts your home directory to `/host/home/...`,
  # so as long as the WebKit checkout is somewhere within your $HOME,
  # mapping paths is as easy as just prepending `/host`
  let mappings_table = ["Source" "WebKitBuild/GTK/Debug" "WebKitBuild/GTK/Release"]
    | each {|path| {host: $"($env.WEBKIT_DIR)/($path)" container: $"/host($env.WEBKIT_DIR)/($path)"}}

  let mappings = $mappings_table
    | each {|it| $"($it.host)=($it.container)" }
    | str join ","

  let podman_args = [
    exec
    --detach
    --user
    1000:100
    $name
  ]

  let clangd_args = [
    $"--path-mappings=($mappings)"
    --header-insertion=never  # clangd has the tendency to insert unnecessary includes, so I prefer to just disable the feature.
    --limit-results=5000      # The default limit for reference search results is too low for WebKit
    --background-index
    --enable-config           # Enable reading .clangd file
    -j 8
  ]

  # Show results of above configuration when called with --show-config, particularly helpful for debugging
  if $show_config {
    {
      port: $port
      work_dir: $workdir
      mappings: $mappings_table
      podman_args: $podman_args
      clangd_args: $clangd_args
    }
  } else {
    # ensure that the container is running
    podman start $name | ignore

    # container side
    ( podman ...$podman_args /usr/bin/env $'--chdir=($workdir)' socat 
      $"tcp-l:($port),fork"
      $"exec:'clangd ($clangd_args | str join (char space))'"
    ) | ignore

    # host side
    nc localhost $port
  }
}

Step 2: IDE setup #

IDE setup is largely the same as it would usually be, aside from pointing the clangd path at our wrapper script instead. I use helix, where I just need to add a .helix/languages.toml to the WebKit checkout directory:

[language-server.clangd]
command = "/path/to/clangd_wrapper"

In VS Code, you need the clangd extension, then you can enter the absolute path to the script under File > Preferences > Settings > Extensions/clangd > Clangd: Path, ideally in the Workspace tab so the setting only applies to WebKit.

Step 3: clangd setup #

Clangd will require two things to be set up at the root of your WebKit checkout:

First, create a compile_commands.json symlink for the build you will use, for example to WebKitBuild/GTK/Debug/compile_commands.json.

Secondly, a .clangd (which is what we needed the --enable-config flag for) at the root of the WebKit checkout:

If:
PathMatch: "(/host/home/vivienne/dev/work/metro/wk/up)?Source/.*\\.h"
PathExclude: "(/host/home/vivienne/dev/work/metro/wk/up)?Source/ThirdParty/.*"

CompileFlags:
Add: [-include, config.h]

I created both files manually, but as of [cmake] Auto-complete via clangd auto-setup, there seem to be new scripts to help with setting up and updating both files. (Thanks Alicia!) I haven’t tried it so far, but I recommend you take a look yourself.

Conclusion #

Example of using the symbol picker in Helix in a WebKit header file - there is a big popup across the entire window, showing results of a fuzzy search on the left, and a snippet of the selected item on the right Searching for a field using the symbol picker

Overall, I’m very satisfied with the results, so far everything is working like I expected it to. Finally having a working language server brought me the usual benefits - I mostly got rid of the manual compile-fix cycles that introduced so much friction and waiting times, and trivial mistakes and typos are much less of a headache now. But the biggest improvement, to me, is Goto definition/references and the symbol picker, making it easier to grasp how things interact. Much better than using grep over and over!

As I was fighting clangd/podman, I also came across some other options that I didn’t try, but might be interesting to look at:

  1. VSCode dev containers
    Probably the most polished option, though it is exclusive to VSCode - from what I understand, the extension isn’t even available to forks for licensing reasons.

  2. Distant
    Its main purpose is to act as a tool for working remotely, but I don’t see why it couldn’t be used with a container. It is still in alpha, and so far only has support in Neovim. I can’t tell how well it would play with LSP, but it might be worth a shot if you already use Neovim.