A OneWare Studio plugin that runs FPGA toolchains (GHDL, Yosys, nextpnr, gmpack, Icarus, Verilator, SymbiYosys) inside containers without changing the user's workflow. It plugs into OneWare's pluggable tool-execution strategy, maps the project into a container, runs the tool there with its paths mapped into the container, and streams output back to the IDE, so a build behaves identically across machines with no host toolchain install.
Developed as part of the Master's thesis Design and Implementation of a Modular Architecture for the Transparent Integration of Containerized Execution Environments for Heterogeneous Open-Source Binaries in OneWare Studio by Mert Torun at TH Köln (2026), openly available at https://doi.org/10.57683/EPUB-3597. The code has changed since the thesis was submitted.
- From OneWare Studio: open Extras > Extensions, search for "Container Extension", and install. On
OneWare Studio before 1.0.43, restart it once, so that the Container Dashboard's tab and menu entry
appear. If the search finds nothing, add the extension's manifest as a package source first; the
getting-started guide gives the link. The plugin ships as a managed
net10.0assembly; OneWare provides the runtime. - Side-load a local build: publish the plugin and copy it into the OneWare plugins directory
(
~/OneWareStudio/Packages/Plugins/on Linux/macOS). See docs/articles/getting-started.md.
OneWare Studio 1.0.45 or later is required; the plugin is built against OneWare.Essentials 1.0.45 and
implements the tool-execution strategy interface that OneWare introduced in 1.0.40. A running container
engine (Docker, Podman, OrbStack, or Colima) is required for containerized execution. Containerized runs use the toolchain image
fentwums/oss-cad-suite:latest, which is on no registry: build it once with Build Local Image in the
Container Dashboard, or with docker/build_oss_cad_suite.sh.
Each top-level directory carries its own README with the detail; this is the map.
| Path | Contents |
|---|---|
src/ |
The plugin assemblies. ContainerExtension is the core: IToolExecutionStrategy/DockerExecutionStrategy, the registry client, settings, telemetry, and the Avalonia Docker dashboard (net10.0, IsAotCompatible, source-generated JSON and regex). |
tests/ |
ContainerExtension.UnitTests (xUnit: validators, command building, path mapping, telemetry, registry parsing, SSRF guards, plus gated container E2E). |
docker/ |
Build inputs for the hardened fentwums/oss-cad-suite toolchain image: the digest-pinned Dockerfile, build_oss_cad_suite.sh, and pull_all_images.sh. |
docs/ |
DocFX site sources — prose guides under articles/ and the generated API reference. See Documentation to build and view the HTML. |
The runtime design (the execution-strategy integration, the container lifecycle, the hybrid fallback) is summarized under Execution model and detailed in docs/articles/architecture.md.
The plugin coordinates execution through a hybrid strategy:
---
config:
flowchart:
wrappingWidth: 400
---
flowchart TD
A[OneWare runs an FPGA tool] -->|dispatches to the selected<br/>IToolExecutionStrategy| B[DockerExecutionStrategy]
B --> C{Container engine<br/>reachable?}
C -->|yes| D[Resolve image<br/>env var → per-call → per-tool →<br/>plugin → default → built-in fallback]
D --> E[Map project into /workspace<br/>host UID/GID via --user,<br/>not on Windows or rootless<br/>cap-drop ALL · no-new-privileges<br/>PID cap]
E --> F[Run the tool<br/>in the container<br/>tini as PID 1]
C -->|no, native fallback enabled| G[Run the tool<br/>from the host PATH]
C -->|no, fallback disabled| H[Fail: daemon<br/>unreachable]
F --> I[Stream stdout/stderr to the IDE<br/>with the tool's exit code]
G --> I
I --> J[Record JSON Lines telemetry<br/>at the configured level]
- Containerized (opt-in per tool): each tool's Execution Strategy setting defaults to native; selecting
the Docker strategy routes that tool through a container, which pulls and runs the toolchain image, pinning
the container process to the host UID/GID via
--userso output files are not root-owned. On rootless runtimes and on Windows hosts--useris left out and the image's default user applies,onewarein the toolchain image.tiniruns as PID 1 to reap children and forward signals. - Native fallback: if the daemon is unreachable and
Allow Native Fallbackis enabled, the tool is located on the hostPATHand run natively so work is not blocked. - Telemetry: execution records are written as JSON Lines with a configurable level (
Off,Errors Only,Info,Verbose) and retention. See docs/articles/telemetry.md.
- Containers run as the host UID/GID via
--user, except on rootless runtimes and Windows hosts, where the image's default user applies (onewarein the toolchain image);tinias PID 1. Every container drops all capabilities (--cap-drop=ALL), forbids privilege escalation (--security-opt no-new-privileges), and caps the task count as a fork-bomb backstop. - Host paths that escape the mounted workspace are remapped to an in-workspace sentinel rather than their real location; an explicit device/library allowlist is the only pass-through. The behaviour is pinned by an adversarial path-containment test corpus that runs in CI.
- A mount deny list rejects binds of critical host paths (
/etc,/proc,/sys,/run, the Docker socket, …); on Linux a project on a removable drive under/run/media/, where udisks2 mounts it, may be bound. - The registry client uses HTTPS, and plain HTTP only for a registry named as
localhostor a loopback address; it scopes forwarded credentials to the matching host and rejects any other host name that resolves to a loopback or internal address (SSRF defense). - Supply chain:
NuGetAudit, an SBOM and OIDC build attestations on releases, and CodeQL plus Trivy scans in CI. The runtime image pins its base by digest and verifies the toolchain tarball against a committed SHA-256 (fail-closed); image integrity is anchored on these committed digests and the per-run recorded image digest. Provenance is TLS-trusted, not yet cosign-signed.
| Runtime | Endpoint | Notes |
|---|---|---|
| Docker | /var/run/docker.sock, ~/.docker/run/docker.sock; on Linux also ~/.docker/desktop/docker.sock (Docker Desktop) and /run/user/{uid}/docker.sock (rootless) |
Probed first |
| Podman | /run/user/{uid}/podman/podman.sock, podman-machine |
Rootless |
| OrbStack / Colima | per-runtime sockets | Probed via a priority list |
| Custom | Custom Daemon Socket setting |
Overrides probing |
On Windows the daemon is reached over the named pipe; the connection path is validated before use.
git clone --recurse-submodules https://github.com/FEntwumS/FEntwumS.ContainerExtension.git
cd FEntwumS.ContainerExtension
dotnet format OneWare.ContainerExtension.slnx --verify-no-changes
dotnet build OneWare.ContainerExtension.slnx -warnaserror -c Release
dotnet test OneWare.ContainerExtension.slnx -c ReleaseThe container E2E tests are skipped in CI (Docker Hub rate limits and image-pull flakiness). What they need to run locally is listed under Running the container tests.
Prose guides live under docs/articles: getting started, configuration, architecture, telemetry, and limitations. The full site bundles those guides with an API reference generated from the source by DocFX:
dotnet tool install -g docfx # once
docfx docs/docfx.json --serve # build and serve at http://localhost:8080docfx docs/docfx.json (omitting --serve) regenerates the API metadata and produces the static HTML under
docs/_site/. The bare docfx build subcommand skips metadata generation, so run the full command above for
a complete site with the API reference.
If you use this software, cite it via CITATION.cff.
MIT © 2025–2026 Mert Torun