Skip to content

Packaging: deb, rpm, AppImage and Flatpak for simd - #35

Merged
Spacefreak18 merged 9 commits into
Spacefreak18:masterfrom
MrDavid5465:feature/packaging
Sep 7, 2026
Merged

Packaging: deb, rpm, AppImage and Flatpak for simd#35
Spacefreak18 merged 9 commits into
Spacefreak18:masterfrom
MrDavid5465:feature/packaging

Conversation

@MrDavid5465

Copy link
Copy Markdown
Contributor

Builds simd and libsimapi as .deb, .rpm, AppImage and Flatpak in CI, keeping the libsimapi / simd split and the per-distro control files already in tools/distro. All seven jobs green, and the result has been installed and run on a real system.

This exists because simd was being bundled into monocoque's Flatpak, and you'd rather it stayed separate. Agreed — but a Flatpak monocoque still needs a simd it can reach, so simd needs packaging of its own. On an immutable host a .deb or .rpm isn't installable, which is why the Flatpak matters.

Read this part before the rest

The Flatpak can run arbitrary commands on the host, and its sandbox is not a security boundary.

That's inherent to what simd does. It identifies a running sim by scanning /proc for known process names and reads /proc/<pid>/environ for the Steam compat variables behind the auto-bridge. Flatpak gives every sandbox its own PID namespace with no way to share the host's — measured 640 processes visible via the host against 4 inside — and /proc can't be bound in ("Path /proc is reserved by Flatpak"). flatpak-spawn --host is the only mechanism that reaches it, and it's general purpose by nature.

So --talk-name=org.freedesktop.Flatpak is doing real work and is the reason the bundle isolates nothing. The manifest and metainfo both say so plainly. If you'd rather not carry that, the honest alternative is a --sim flag so simd can be told what to map, which would let a sandboxed simd work with only --device=shm — at the cost of auto-detection and the auto-bridge.

No source changes were needed for packaging

SYSTEM_USER_UNIT_DIR and SIMD_CONFIG_DIR are already CACHE PATH, so packaging redirects them off $ENV{HOME} with -D flags rather than patching install rules.

The one runtime change is host routing: the sim lookup, environ read, both liveness checks, notify-send and the bridge exec go through flatpak-spawn --host, guarded on /.flatpak-info so a native build takes the original paths untouched. Three things it had to work around are documented in that commit — a process named AC: main thread can't be parsed from a combined ps line, a pipe to flatpak-spawn never reaches EOF, and a trailing redirect in sh -c binds only to the last command of a sequence.

Changes to your existing packaging

  • Both specs' %prep cloned the default branch unconditionally, so an rpm's contents tracked master rather than the tag being built. They now use a staged tree when CI provides one and clone only as a fallback, so a bare rpmbuild -ba is unchanged.
  • simd.spec ships the unit and simd.config as an example, and sets CMAKE_SKIP_RPATHcheck-rpaths rejects the binary for a build-tree RUNPATH, and %global __brp_check_rpaths %{nil} no longer suppresses that on Fedora 43/44.
  • tools/distro/simd.service is a packaged unit. Yours sets Type=simple against a daemon that double-forks, so systemd takes the parent's exit as the service failing while the real daemon carries on unsupervised, and it hardcodes ExecStart=%h/.local/bin/simd. The packaged one runs /usr/bin/simd -n.
  • tools/distro/simd.svg and simd-128.png are placeholder artwork, present only because linuxdeploy and appstream require an icon. Replace whenever.

Verified

All seven jobs green. On a Bazzite host, installed alongside the monocoque and monocoque-builder Flatpaks: simd starts, loads its config, opens the universal shared memory, publishes /dev/shm/SIMAPI.DAT visible to both other sandboxes, sees the host process table, detects a running sim, and the shakers went active.

Contains the two commits from #34; happy to rebase once that lands.

🤖 Generated with Claude Code

MrDavid5465 and others added 9 commits September 3, 2026 18:04
Some distro packagings -- the freedesktop Flatpak SDK among them -- ship a
libproc2.pc whose Version field is the literal string UNKNOWN. Compared
against that, VERSION_GREATER_EQUAL is always false, so the build fell
through to the pre-4.0.5 four-argument PIDS_VAL API and failed to compile
against a current libproc2 whose header moved to three arguments long ago.

Take the old-API branch only when the version string really parses and
really is old.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
simapi identifies a running sim by scanning /proc for known process names
and reads /proc/<pid>/environ for the Steam compat variables behind the
auto-bridge. Flatpak gives every sandbox its own PID namespace and offers no
way to share the host's: measured 634 processes on the host against 4 inside
a sandbox, unchanged by --allow=devel or --filesystem=host, and /proc cannot
be bound in either ("Path /proc is reserved by Flatpak"). simd has no flag
to be told which sim to use, so a sandboxed simd simply never finds one.

The sim lookup, the environ read, both liveness checks, notify-send and the
bridge exec now route through `flatpak-spawn --host`, guarded on the
presence of /.flatpak-info so a native build takes the original code paths
unchanged.

Three things this had to work around:

- The host process table is read with two tagged `ps` runs (args and comm)
  rather than one combined format, because a process whose name contains
  spaces -- "AC: main thread" -- cannot be parsed unambiguously from a
  single line.
- Output comes back through a file staged in $XDG_RUNTIME_DIR/app/$FLATPAK_ID
  and renamed into place, not through popen: a pipe to `flatpak-spawn --host`
  never reaches EOF, so the read hangs.
- The redirection is written as `{ ...; } > out.part` because in `sh -c`
  a trailing redirect binds only to the last command of a sequence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No upstream CMake changes were needed. SYSTEMD_USER_UNIT_DIR and
SIMD_CONFIG_DIR are already CACHE PATH variables, so packaging redirects
them off $ENV{HOME} rather than patching the install rules.

Each format ships /usr/bin/simd, the libsimapi SONAME chain simd links at
runtime, and simd.config as an example -- a package must not write into a
user's home, which is what upstream's defaults do.

packaging/simd.service replaces the shipped unit for deb and rpm. Upstream's
conf/simd.service sets Type=simple against a daemon that double-forks, so
systemd would take the parent's exit as the service failing while the real
daemon carried on unsupervised, and it hardcodes ExecStart=%h/.local/bin/simd
with LD_LIBRARY_PATH=%h/.local/lib, which is only correct for a build
installed into ~/.local. The packaged unit runs /usr/bin/simd -n under
systemd's own supervision.

The Flatpak has no unit -- autostart is a ~/.config/autostart entry running
`flatpak run`, the same pattern other Flatpak background services use. Its
desktop entry passes -n for the same reason: the sandbox is torn down when
the process flatpak started exits, so a double-forking simd would take its
own daemon down with it.

The manifest documents plainly that this bundle can run arbitrary host
commands and is therefore not a security boundary. That is inherent to what
simd does: it identifies a sim by reading the host process table, which a
sandbox cannot see (634 host processes against 4 inside, /proc unbindable),
and flatpak-spawn --host is the only mechanism that reaches it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Builds on the packaging already in tools/distro rather than a parallel tree:
the per-distro control files and the two Fedora specs are used as they
stand, with only the version stamped from the tag. The split into libsimapi
and simd is upstream's and is kept.

Both specs' %prep cloned the default branch unconditionally, so an rpm's
contents tracked master rather than the tag being built -- a fix on the
branch being released would be absent from its own release. They now build a
staged tree when CI provides one and clone only as a fallback, so a bare
`rpmbuild -ba` still behaves as before.

simd.spec additionally ships the packaged systemd unit and simd.config as an
example, and redirects SYSTEMD_USER_UNIT_DIR and SIMD_CONFIG_DIR off
$ENV{HOME}. Neither needed an upstream code change; both are already CACHE
PATH variables.

The AppImage carries the config but not the unit -- it cannot install one
onto the host. tools/distro/simd.svg is a placeholder, present only because
linuxdeploy requires an icon and this repository has none.

The Flatpak job installs flatpak-builder with Recommends left enabled. Those
recommends are the helpers it shells out to at build time -- tar's
decompressors, eu-strip, patch -- and stripping them produces a builder that
runs, builds several modules, then fails on whichever helper the next module
happens to need.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three unrelated failures from the first run; the deb legs passed unchanged.

rpm: Fedora packages neither yder nor orcania -- `dnf install yder-devel`
fails with "No match for argument" on 43 and 44, while Debian and Ubuntu
ship libyder-dev. Both are now vendored and built as static libraries, so
simd links them without leaving a runtime dependency Fedora cannot satisfy
and the spec's Requires: stays as upstream wrote it.

AppImage: ubuntu-22.04 cannot build this at all. It ships libprocps rather
than libproc2, so getpid.c fails on a missing <libproc2/pids.h>, and its GCC
predates the C23 fixed-underlying-type enums in include/F12018/*.h. Moved to
24.04, which is now the oldest base that works.

Flatpak: orcania and yder both installed to /app/lib64, so when yder
configured, pkg-config did not find liborcania.pc under /app/lib/pkgconfig
and yder linked without orcania. That surfaced much later and looked like
something else entirely -- simd failing to link with undefined references to
o_malloc, o_free, o_strdup and split_string attributed to libyder.so. Both
modules now install to /app/lib.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Second round; the deb legs stayed green throughout and the previous fixes
all held -- each of these is a new failure further along.

rpm: CMAKE_CXX_COMPILER not set. simd/CMakeLists.txt uses
CheckCXXCompilerFlag, which enables the CXX language, and Fedora's `gcc`
package does not provide a C++ compiler. Added gcc-c++.

AppImage: "Could not find dependency: libsimapi.so.1". simd links a library
that exists only inside the AppDir, so linuxdeploy's ldd-based resolution
could not find it. LD_LIBRARY_PATH now points at the staged AppDir.

Flatpak: appstreamcli compose failed. The desktop entry named an icon that
was never installed, so the component referenced an icon that did not exist,
and NoDisplay=true would have had compose discard the component anyway,
leaving the bundle with no metadata. The icon is now installed and the entry
is listed -- which also suits how this is meant to be started, since a
desktop's "startup applications" picker will not offer a NoDisplay entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two of the three remaining failures were the same mistake made twice, so
they are fixed generally rather than per-leg.

CMake's GNUInstallDirs picks a different library directory on every platform
-- lib64 in the freedesktop runtime, lib/x86_64-linux-gnu on Ubuntu -- and
both guesses were already wrong once. In the Flatpak, orcania and yder
landed in lib64 where yder's own configure could not find liborcania.pc; in
the AppImage, libsimapi landed under the multiarch triplet while linuxdeploy
looked in AppDir/usr/lib. CMAKE_INSTALL_LIBDIR is now pinned to lib wherever
this project controls the build.

simd links yder, and yder calls into orcania. A shared libyder.so records
that dependency, which is why Debian and Ubuntu never needed anything: their
link just follows it. A static libyder.a records nothing, so o_malloc,
o_free, o_strdup, o_strnullempty and split_string all come back undefined --
which is what the Fedora rpm hit, since Fedora packages neither library and
the job vendors both as static archives. simd/CMakeLists.txt now names
orcania explicitly when it is present, after yder, as a static link needs.

appstreamcli compose was failing on the metainfo: no developer element, a
description opening on a lowercase word, and an icon that existed only as
SVG. A 128x128 raster is now installed alongside it. Verified with
appstreamcli validate in the freedesktop SDK -- the only remaining finding
is url-not-reachable, which is the sandbox having no network.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Flatpak's "appstreamcli compose failed" gave no detail in the build log,
so this run was diagnosed by staging a prefix and running compose directly
in the freedesktop SDK. It names the cause outright:

  E: file-read-error
     Unable to read data from file
     `/usr/share/icons/hicolor/scalable/apps/io.github.spacefreak18.simd.svg`:
     Unrecognized image file format

appstreamcli in that runtime cannot decode the SVG at all, so installing it
alongside the PNG was not a fallback, it was the failure. Removing it makes
compose report Success against the same staged prefix. The bundle now ships
the 128x128 raster only.

AppImage: "Could not find suitable icon for Icon entry". linuxdeploy takes an
icon's name from its filename, so simd-128.png was deployed as "simd-128"
while the desktop entry names io.github.spacefreak18.simd. Copied to the
matching filename before deploying.

rpm: check-rpaths rejected /usr/bin/simd for a RUNPATH pointing into the
build tree, which CMake bakes in because simd links libsimapi from there.
The spec's %global __brp_check_rpaths %{nil} no longer suppresses that on
Fedora 43/44, so CMAKE_SKIP_RPATH stops it being emitted instead. Nothing
needs it: libsimapi installs to a standard system library directory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…esult

The bundle built, packaged and uploaded cleanly, then failed the moment it
was installed and started:

  simd: error while loading shared libraries: libuv.so.1: cannot open
  shared object file: No such file or directory

libuv and libconfig installed to /app/lib64 while everything else was in
/app/lib, and only /app/lib is on the runtime's library search path. The
previous fix pinned CMAKE_INSTALL_LIBDIR on three modules; it needed to be
all six.

The reason CI was green is that nothing in that job ever executed what it
built. It now installs the bundle and runs `simd --version`, which is enough
to prove every shared library resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Spacefreak18

Copy link
Copy Markdown
Owner

we can merge this although, having code for different build types scares me for long term maintainability

@Spacefreak18
Spacefreak18 merged commit 9ec43bd into Spacefreak18:master Sep 7, 2026
@Spacefreak18

Copy link
Copy Markdown
Owner

the other issue is in the "stage" directory.

  1. The ".h" files, i wonder if we could just have those be symlinks to the actual .h files.
  2. does it even have to check in and create binary files in the repo.

@Spacefreak18

Copy link
Copy Markdown
Owner

the other issue is in the "stage" directory.

  1. The ".h" files, i wonder if we could just have those be symlinks to the actual .h files.
  2. does it even have to check in and create binary files in the repo.

same goes for the config and .pc files which exist elsewhere

@Spacefreak18

Copy link
Copy Markdown
Owner

the other issue is in the "stage" directory.

  1. The ".h" files, i wonder if we could just have those be symlinks to the actual .h files.
  2. does it even have to check in and create binary files in the repo.

nevermind, it was just an artifact that got checked in by mistake. I fixed it retroactively, but it might force you to blow away your repo next time you work on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants