velxio/docs/BUILD-QEMU.md

9.2 KiB

Building QEMU from source for Velxio

The Velxio docker image ships with prebuilt libqemu-xtensa and libqemu-riscv32 libraries so ESP32 / ESP32-S3 / ESP32-C3 simulation works out of the box. If you'd rather not use the prebuilts — whether for licensing reasons, audit requirements, or simply because you prefer to compile what you run — this guide tells you how to get the source they were built from and how to build it.

QEMU is GPL-2.0, and so is every line Velxio adds to it. The complete corresponding source for the binaries we distribute is available to anyone who receives those binaries, with no additional conditions attached — see Getting the source.

Read this first: upstream will not build what Velxio runs

Velxio's ESP32 support does not come from an unmodified upstream checkout, and building lcgamboa/qemu will not produce a drop-in replacement. Concretely:

  • Upstream's picsimlab-esp32 branch does contain hw/xtensa/esp32s3.c, but it registers Espressif's plain -M esp32s3 machine. Velxio instantiates esp32s3-picsimlab, a variant that wires the host-side GPIO/SPI/I2C/UART callbacks the Python worker binds to. Upstream has no such machine, so a stock build starts and then fails to find the machine Velxio asks for.
  • Several device models Velxio depends on exist only in our tree: hw/ssi/esp32s3_gpspi.c (the S3's GPSPI2 controller — the plain machine leaves it unimplemented, so SPI displays have no controller to talk to), hw/i2c/esp32_ov2640.c, hw/misc/esp32_i2s_cam.c and hw/misc/velxio_camera_export.c (the ESP32-CAM path).

So: build from the source archive described below, not from an upstream clone. Earlier revisions of this document told you to check out a specific upstream commit and claimed the result would match our prebuilts byte-for-byte. Both claims were wrong, and the commit id they named never existed. Sorry — that was our error, and it cost people real build cycles.

Audience

This guide assumes a Linux or macOS workstation with a working C toolchain. Windows builds go through MSYS2 / MinGW — see build_libqemu-esp32-win.sh and the windows job in .github/workflows/build-libqemu.yml inside the source archive.

What you'll produce

File Architecture Used for
libqemu-xtensa.so Xtensa LX6 / LX7 ESP32, ESP32-S3, ESP32-CAM, Arduino Nano ESP32
libqemu-riscv32.so RISC-V RV32IMC ESP32-C3, XIAO ESP32-C3, CH32V003

The Velxio backend dlopens these at runtime through backend/app/services/qemu_runtime.py. Drop the freshly built files into /app/lib/ inside the container (or the host path that mounts to it) and Velxio will use them on the next simulation start.

1. Getting the source

The corresponding source is published as a tarball alongside the binaries themselves, on the same download endpoint:

# List what your key can fetch — source archives are named
# qemu-source-<commit>, matching the commit each binary was built from.
curl "https://velxio.dev/api/pro/license/downloads?key=$VELXIO_LICENSE_KEY"

# Fetch one.
curl -O -J \
  "https://velxio.dev/api/pro/license/downloads/qemu-source-83a944ce?key=$VELXIO_LICENSE_KEY"

Free personal keys are at velxio.dev/license/signup. The listing reports a sha256 for each asset — check it.

The archive contains the full tree, the build scripts, the release CI workflow, and a README.VELXIO-SOURCE.md recording which binaries it corresponds to and what the tree adds over upstream. It is GPL-2.0: yours to study, modify and redistribute under those terms.

If you received a Velxio binary and cannot reach the endpoint for any reason, open an issue — you are entitled to the source and we will get it to you.

2. Install build dependencies

Debian / Ubuntu:

sudo apt-get update
sudo apt-get install -y --no-install-recommends \
    build-essential git wget curl \
    python3-pip python3-setuptools \
    ninja-build pkg-config flex bison \
    libglib2.0-dev libgcrypt-dev libslirp-dev \
    libpixman-1-dev libfdt-dev zlib1g-dev jq
pip3 install meson==1.2.3 tomli

tomli is not optional on Python older than 3.11: QEMU 9.2's mkvenv needs it, and configure dies with "found no usable tomli" without it.

On glibc, iconv lives in libc and there is no separate library, but QEMU's configure adds -liconv regardless. An empty stub satisfies the linker:

LIBDIR=$(dpkg-architecture -q DEB_HOST_MULTIARCH)
sudo ar rcs /usr/lib/${LIBDIR}/libiconv.a

macOS (Apple Silicon):

brew install gnu-sed coreutils ninja pkg-config meson \
             glib pixman libgcrypt libslirp
python3 -m pip install --user --break-system-packages distlib
export PYTHONPATH="$(python3 -c 'import site; print(site.getusersitepackages())')"
export PATH="$(brew --prefix gnu-sed)/libexec/gnubin:$PATH"
export OBJC=clang OBJCXX=clang++

GNU sed must come first on PATH — the build script relies on GNU sed -i semantics. Homebrew's Python does not bundle distlib, which QEMU's mkvenv.py requires.

3. Build

Both libraries come out of one script — the same one the release CI runs:

tar xzf velxio-qemu-src-<commit>.tar.gz
cd velxio-qemu-<commit>
bash build_libqemu-esp32.sh

Do not try to hand-roll the configure line. QEMU has no --enable-shared-lib option (earlier revisions of this document invented one); the library is produced by building qemu-system-* normally and then relinking the same objects without system_main, which is exactly what the script does. It also sets the -DESP32_PICSIMLAB_SOFT_CACHE=1 flag the ESP32 machines expect.

On macOS the script still writes libqemu-*.so; rename to .dylib.

Verify:

file build/libqemu-xtensa.so
# ELF 64-bit LSB shared object, x86-64, dynamically linked

ls -lh build/libqemu-xtensa.so build/libqemu-riscv32.so
# ~45-50 MB each on Linux x86_64

4. Drop the binaries into Velxio

The worker passes -L <directory of the .so> to QEMU, so the boot ROM blobs must sit beside the library, not elsewhere:

/app/lib/libqemu-xtensa.so      /app/lib/esp32-v3-rom.bin
/app/lib/libqemu-riscv32.so     /app/lib/esp32-v3-rom-app.bin
                                /app/lib/esp32c3-rom.bin
                                /app/lib/esp32s3_rev0_rom.bin

If you self-host via the official docker image:

docker cp build/libqemu-xtensa.so   velxio:/app/lib/libqemu-xtensa.so
docker cp build/libqemu-riscv32.so  velxio:/app/lib/libqemu-riscv32.so

Workers load the library on every run, so no restart is needed — the next simulation start picks it up. Running from source instead, copy into backend/app/lib/.

You can also skip the container copy entirely: drop your .so files plus the four ROM blobs into prebuilt/qemu/ before docker compose build, and the image build will use them instead of downloading anything.

5. ESP32 ROM blobs

The QEMU build does not produce the boot ROM dumps Velxio also needs (esp32-v3-rom.bin, esp32-v3-rom-app.bin, esp32c3-rom.bin, esp32s3_rev0_rom.bin). Those come from Espressif's open-source toolchain and are redistributable verbatim. The image already includes them at /app/lib/; you only need to replace them if you're working from a custom esp-idf version.

6. Troubleshooting

found no usable tomli — Python < 3.11 without tomli. See the dependencies step.

No such file or directory: glib-2.0 — apt missed libglib2.0-dev.

Linker error mentioning -liconv on Linux — create the empty libiconv.a stub described above.

a usable distlib could not be found on macOS — Homebrew Python without distlib; install it and export PYTHONPATH.

sed: -i may not be used with stdin or similar on macOS — BSD sed is ahead of GNU sed on PATH.

The library is only a few hundred KB — the relink step didn't run. Read build/ninja-link-*.log, which the script keeps.

Velxio starts the simulation and QEMU reports an unknown machine (esp32s3-picsimlab, esp32-picsimlab, esp32c3-picsimlab) — you built from an upstream clone rather than the source archive. See the warning at the top of this document.

License notes

QEMU — including our modifications — is GPL-2.0. Velxio itself is AGPLv3. The dlopen boundary keeps the two licenses orthogonal: QEMU stays GPL'd, Velxio stays AGPL'd, neither contaminates the other.

If you redistribute the QEMU libraries, you owe your recipients the corresponding source under GPL-2.0 (the archive above is exactly that). If you deploy a modified Velxio over a network, you owe your users the Velxio modifications under AGPLv3.

Why use the prebuilts at all?

For most self-hosters the prebuilts are the path of least resistance — already built, checksummed in their manifest, ready to drop in. The build takes 15-30 minutes on a modern laptop and ~3 GB of disk.

Building from source matters when:

  • You're auditing the supply chain for a regulated deployment.
  • You need to patch QEMU — add a peripheral, fix a device model — and want to ship the patched library.
  • You don't want third-party prebuilts on your machine.
  • You're on a platform we don't ship a binary for (BSD, exotic libc).

All four are legitimate, and none of them require asking us first.