Skip to content

Packaging: containerised CI, Flatpak/AppImage, bundled simd, licence notices, and release-please - #53

Open
MrDavid5465 wants to merge 46 commits into
Spacefreak18:masterfrom
MrDavid5465:feature/packaging-release
Open

Packaging: containerised CI, Flatpak/AppImage, bundled simd, licence notices, and release-please#53
MrDavid5465 wants to merge 46 commits into
Spacefreak18:masterfrom
MrDavid5465:feature/packaging-release

Conversation

@MrDavid5465

Copy link
Copy Markdown
Contributor

Packaging work for monocoque: deb, rpm, AppImage and Flatpak all built and released from CI, with simd bundled alongside. Every leg has been run green, and the Flatpak has been installed and used on a real Bazzite system.

It's a large branch because the pieces depend on each other — release-please is only useful once CI can actually run, and the Flatpak is only useful once simd can see the host. Happy to split it if you'd rather review in stages.


What you'd need to enable (two settings, nothing in code)

1. Required for release-please: Settings → Actions → General → Workflow permissions → tick "Allow GitHub Actions to create and approve pull requests." Without it the action fails the first time it tries to open a release PR. This is the only mandatory one.

2. Optional, but you'll want it: Settings → Actions → General → Fork pull request workflows → set contributor approval to "Require approval for first-time contributors who are new to GitHub." Release-please's PR is authored by github-actions[bot], and under the stricter default its checks sit at "waiting for approval" until someone clicks. Loosening this means fork PRs from established GitHub accounts start workflows without a click — the token there is read-only and secrets aren't exposed, so the practical risk is compute, not credentials.

On merge style: squash works best. The squash commit message is what release-please reads, so contributors never have to learn the commit format — you set or fix the PR title when merging. It also keeps history linear with no merge commits.

Release-please

Merging a feat:/fix: PR opens a release PR; merging that tags the version, writes CHANGELOG.md, and publishes all the packages. Configured to fit this repo rather than impose anything:

  • Bare semver tags with no v prefix, continuing the existing 79 tags rather than starting a parallel series. Seeded at 0.3.6.
  • While the major version is 0, a breaking change bumps the minor — reaching 1.0.0 stays your deliberate call.
  • Only feat and fix appear in the notes. refactor, ci, chore, docs and friends are recognised and hidden; flip hidden in release-please-config.json to surface any of them.
  • Commits that don't match the format are ignored, not errors. Adoption can be gradual and old history doesn't matter.

ci.yaml becomes callable rather than gaining a second tag trigger. A tag pushed with the default GITHUB_TOKEN does not start a workflow — GitHub blocks that to stop workflows triggering each other — so push: tags never fires for a release-please tag. Calling ci.yaml from the same run keeps the release in one place and needs no personal access token on a public repo. A hand-pushed tag still works exactly as before.

CI actually runs now

The deb/rpm matrices used runs-on: ${{ matrix.os }} with debian-latest/debian-stable as runner labels, which only resolve to self-hosted runners. With none online those legs queued indefinitely — observed four runs stuck 17–60 minutes, only the ubuntu-latest leg ever finishing. They now build in containers on ordinary GitHub-hosted runners. Fedora 44 added alongside 43.

Package versions are stamped from the tag. The control files carried a hardcoded Version: 1, so every .deb ever released claimed version 1 regardless of its tag.

Packages

  • deb × 3 (ubuntu-latest, debian-testing, debian-stable) — several dependency names in the control files no longer exist on current targets (libxml2-16, libconfig11, libuv1t64, libcurl4t64, libgtk-3-0t64); corrected per distro.
  • rpm × 2 (Fedora 43, 44) — orcania/yder are vendored and built static, since Fedora doesn't package them.
  • AppImage — built on ubuntu-24.04, since an AppImage is only as portable as the oldest glibc it links.
  • Flatpak — GUI enabled, nappgui and simapi built from the pinned submodules.

simd, and letting it out of the sandbox

simd is built and shipped in every package, so monocoque isn't inert on a fresh install. Inside the Flatpak it also needs to see the host: --device=all doesn't imply /dev/shm, so telemetry was invisible, and process detection ran against the sandbox's own 5-process namespace instead of the host's ~500. Both are handled — the host process table is read via flatpak-spawn --host in a way that survives process names containing spaces.

Bug fixes found along the way

  • sounddev_init fell off the end of an int function without returning, so USB shakers didn't survive setup.
  • Parameters/MonocoqueSettings were malloc'd and not zeroed, so --help freed garbage.
  • gmonocoque segfaulted on a config with no API key.

Licence notices

The Flatpak and AppImage bundle dependencies, so their copyright files ship with them: per-component licences in the Flatpak, and an appimage-collect-licenses.sh pass that maps bundled libraries back to their dpkg copyright files (47 collected on the last run) plus a BUNDLED-LIBRARIES.txt manifest.

One thing worth your attention: the simapi submodule is pinned to 50fd1816, which predates the LICENSE.rst you added in b2e2324e. Bumping the pin would let the bundles carry simapi's own licence text rather than a note about it.

🤖 Generated with Claude Code

MrDavid5465 and others added 30 commits August 25, 2026 15:54
The only existing CI (ci.yaml) triggers on tag push only, so PRs get zero
build or test verification before merge. ENABLE_TESTS looked like a real
test-suite toggle but was dead code: never declared as a CMake option(),
tests/ had no CMakeLists.txt, and the root CMakeLists.txt instead had an
always-on block (outside any ENABLE_TESTS gate) building a handful of
tests/*.c files directly as loose executables - most of it commented out,
and none of what was live was an actual assertion-based test.

Investigating tests/ found nothing there was CI-runnable as-is: three
stray committed compiled binaries (a.out, producer, test.bin), vendored
third-party example code with no relation to monocoque's own logic
(pa_devs.c/patest_longsine.c are PortAudio's own bundled examples;
testlibusb.c/testrevburner.c derive from libusb-0.1-compat's own
testlibusb example), and manual/interactive/hardware-dependent scratch
tools (shared-memory pokers, one of which blocks on raw terminal input;
USB/HID/serial device probes needing physical hardware).

Removed the stray binaries, moved everything else into tests/manual/ so
the split between "automated CI suite" and "manual dev tools" is
structural rather than implicit, and built the actual plumbing:

- ENABLE_TESTS is now a real option(), and the existing
  MONOCOQUE_SUBPROJECT guard block had enable_testing() running *after*
  add_subdirectory(tests) - backwards, meaning CTest would never have
  discovered any test added there. Fixed the order.
- tests/CMakeLists.txt registers one real test via CTest.
- confighelper_test.c: a genuine headless unit test (strtodevsubsubtype's
  string->enum mapping, including the R8/R3->MOZAR5 aliasing and the
  unknown-input fallback) to prove the plumbing actually works end to
  end - not an attempt at comprehensive coverage, just a real first case.
  strtodevsubsubtype already had external linkage but no header
  declaration; added one to confighelper.h.
- .github/workflows/pr-build.yaml: new workflow (existing tag-triggered
  ci.yaml untouched) building the CLI + test suite and running ctest on
  every PR and push to master - closing the verification gap.

Verified locally: both -DENABLE_TESTS=ON and the default (no flag) build
succeed, and `ctest --test-dir build --output-on-failure` passes.
Adds a Flatpak manifest, AppStream metainfo, and CI workflow, mirroring
the sibling typiql-tauri/typiql project's own (now fully proven) Flatpak
pipeline where the same class of issues would otherwise apply -- using
its final recipe from the start (ppa:flatpak/stable for a newer
flatpak-builder with the appstreamcli-compose fallback) rather than
rediscovering the same bugs.

Also adds install() rules to CMakeLists.txt (none existed before; every
existing deb/rpm packaging job hand-copies binaries out of the build
dir instead) and makes tools/monocoque.desktop's Exec= relative
(gmonocoque instead of /usr/bin/gmonocoque) -- both needed for a clean
Flatpak install, and harmless for the existing system packaging since
/usr/bin is always on PATH regardless.

Seven dependencies (libconfig, argtable2, lua5.4, libserialport,
libxdg-basedir, hidapi, GLU) aren't in the freedesktop runtime and are
built from real, checksum-verified upstream sources as flatpak modules.

UNTESTED against real CI at time of writing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed via a failed local build: uv.h missing entirely (libuv wasn't
in the dependency list at all), and a genuine, real CMakeLists.txt bug --
when pkg-config reports libproc2's version as the literal string
"UNKNOWN" (a real packaging quirk, seen in the freedesktop Flatpak SDK,
not flatpak-specific), VERSION_GREATER_EQUAL against it silently
evaluates false and falls through to the old (pre-4.0.5) PIDS_VAL API,
which then fails to compile against an actually-current libproc2 whose
real header already uses the new 3-argument macro. Only take the old-API
branch when the version string is genuinely parseable and actually old.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed via a failed local flatpak build: flatpak-builder's cmake-ninja
buildsystem runs the automatic build+install step regardless of custom
build-commands (they run in addition, not instead), so overriding what
gets built there doesn't work around a target that fails during the
automatic build. simlighttest fails to link (-lserialport not found,
likely a directory-scoping issue since its add_executable() comes after
where the working link setup applies to other targets) and isn't part of
the actual product -- matching its already-commented-out neighbor
(hidtest) and the same "stray scratch tool" characterization from the
unmerged feature/ci-test-automation branch. Disabling it the same way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real CI run 33321176120 failed immediately on entering the monocoque
module: "cp: cannot overwrite non-directory .../simapi/.git with
directory .../flatpak-builder/git/https_github.com_spacefreak18_simapi".
Self-inflicted: submodules: recursive in the checkout step populates
simapi/nappgui_src with real .git files before flatpak-builder even
starts, colliding with the manifest's own explicit `type: git` sources
for those same paths. Local testing (a plain worktree checkout, which
doesn't fetch submodules either) never hit this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real CI run 33322472461 confirmed appstreamcli hard-requires any
desktop-application to have an icon at all (E: gui-app-without-icon),
which the local build only warned about on a more lenient appstreamcli
version. The real monocoque.svg is 100x50 (non-square, rejected by
Flatpak's export). Added a local copy padded to a square 100x100 viewBox,
vertically centering the original artwork via a <g
transform="translate(0,25)"> rather than distorting it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The debian-latest, debian-stable and fedora-43 matrix legs used those
names as `runs-on` labels, which only ever resolve to self-hosted
runners. With none online they queued indefinitely -- four runs observed
stuck between 17 and 60 minutes, only the ubuntu-latest leg ever
completing, until cancelled by hand. Each distro now builds in its own
container on an ordinary GitHub-hosted runner, so no self-hosted
infrastructure is needed.

matrix.os keeps the distro name (it selects the per-distro control file
and names the released artifact); matrix.image is the container. Also
adds a fedora-44 leg alongside 43, and fail-fast: false so one distro
failing no longer cancels the rest.

Supporting changes the container move required:
- dependency install moved ahead of actions/checkout and desudo'd --
  container jobs run as root, and these images ship no git, without
  which checkout silently skips the required submodules
- actions/checkout@v1 -> v4 with submodules: recursive, replacing the
  manual `git submodule update` step
- the rpm job gains a checkout and builds the spec from the tree rather
  than curl'ing it from upstream master, so a tag builds its own spec
- the rpm output filename is globbed instead of hardcoding
  monocoque-0.0.5-1.x86_64.rpm, which only matched by coincidence

Also adds workflow_dispatch for manual runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two failures from the first container run (33324476177):

All three deb legs died at Configure CMake with `The source directory
"/home/runner/work/monocoque/monocoque" does not exist`. In a container
job the ${{ github.workspace }} expression still renders the *host* path,
while the repo is mounted at /__w/... inside the container. Switched to
$GITHUB_WORKSPACE, which is set to the container-side path.

The fedora-43 leg died downloading openh264 from Fedora's third-party
Cisco repo (`Curl error (7): Could not connect to server` for
ciscobinary.openh264.org). Transient rather than structural: fedora-44
pulled the identical package from the same repo successfully in the same
run. Wrapped the dnf install in a 3-attempt retry. Deliberately not
disabling that repo -- openh264 is pulled in even with weak deps off, so
something requires it outright and removing the repo would more likely
break resolution than help.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul asked for an AppImage; this adds one and, per David, puts every
artifact on a single release rather than spreading them across workflows.

The AppImage builds on ubuntu-22.04 rather than ubuntu-latest on purpose:
an AppImage only runs where glibc is at least as new as the one it linked
against, so building on current Ubuntu would silently exclude older
distros and defeat the point of shipping one. That runner also needs
libprocps-dev instead of libproc2-dev, since 22.04 predates the procps-ng
4.x split -- CMakeLists.txt already accepts either. It populates its
AppDir via the install() rules rather than hand-copying binaries the way
the deb job does, which is what those rules were added for.

The standalone flatpak.yml is folded in as a job here and deleted, so a
tag produces deb + rpm + AppImage + Flatpak on one release. It keeps
continue-on-error: the Flatpak leg is the newest and least battle-tested,
and must never turn a release red or block the other artifacts.

The square icon moves from flatpak/ to tools/monocoque.svg, since the
AppImage needs a square icon for exactly the same reason the Flatpak did
and there was no sense duplicating it. Flatpak rebuild re-verified
locally after the move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The AppImage leg failed on ubuntu-22.04 with `expected identifier or '('
before ':' token`. Root cause is the compiler, not packaging: simapi's F1
2018 headers use C23 enum-with-fixed-underlying-type (`enum TrackID :
uint8_t`), which GCC implements only from 13 onwards, and 22.04 ships GCC
11. Every passing leg builds in a container with GCC 15.

No -std= value rescues 22.04 -- verified separately that the construct is
gated on compiler version rather than the standard flag, so the fix is a
newer base rather than a flag. ubuntu-24.04 (GCC 13) is the oldest usable
one, which sets the AppImage's glibc floor at 2.39. Passing -std=gnu2x
explicitly because GCC 13 still defaults to gnu17, unlike the GCC 15 the
container legs get; gnu2x rather than c2x keeps GNU extensions on, which
simapi needs for asprintf. libprocps-dev reverts to libproc2-dev, which
24.04 has.

Worth raising upstream: this GCC 13+ requirement is undeclared, and it
constrains which distros can build monocoque at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… feature/packaging-release

# Conflicts:
#	CMakeLists.txt
flatpak/repo is an OSTree object store -- running flatpak-builder locally
produces ~1000 binary files there, and a `git add -A` swept 13 MB of them
into this branch. Removed from history; ignoring them so it cannot recur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Upstream releases currently ship with empty bodies -- 0.3.5 and 0.3.6 both
have no notes at all. generate_release_notes lets GitHub assemble the
merged PRs and commits since the previous tag, which is the accumulating
changelog that was missing, at the cost of one line per upload step and no
change to how anyone writes commits.

This is deliberately the low-friction option. Proper version management
(release-please) is a larger proposal since it needs Conventional Commits,
which this history does not use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Released packages have never carried their own version: the Debian control
files hardcode `Version: 1` and fedora.spec hardcodes `Version: 0.0.5`,
while real tags are at 0.3.6. Confirmed in shipped artifacts -- an rpm from
tag pkgtest-2 installs as monocoque-0.0.5-1. Both are now stamped from the
tag at build time, which also takes two hand-maintained values out of the
release checklist.

Stamped in CI rather than edited into the files so they remain working
defaults for local builds. Guarded rather than applied blindly: deb and rpm
versions must start with a digit, so a branch build (workflow_dispatch) or
a non-release tag like `pkgtest-5` would otherwise make dpkg-deb and
rpmbuild fail outright. Anything that doesn't look like a version falls
back to 0.0.0; a leading `v` is stripped, so v-prefixed tags would work too.
Guard logic verified against tag/branch/scratch-tag inputs.

The AppImage carries no package metadata, so it gets the version in its
filename instead -- monocoque-<version>-x86_64.AppImage. Both the artifact
upload and release globs already match that.

Each job now also echoes the identity it actually produced (dpkg-deb
--field, rpm -qp) so a wrong version is visible in the log rather than only
discoverable by unpacking the artifact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MrDavid5465 and others added 15 commits August 30, 2026 15:54
gmonocoque died with `cannot open display` under a Wayland session. The
cause is not a missing Wayland socket -- a diagnostic inside the sandbox
confirmed WAYLAND_DISPLAY=wayland-0 with /run/user/1000/wayland-0 present,
and gmonocoque links both libwayland-client and libX11. nappgui goes to X11
regardless, and --socket=fallback-x11 is deliberately inactive whenever
Wayland is available, so DISPLAY was empty and there was no X server to
reach. Granting real --socket=x11 routes it through XWayland.

--socket=wayland is retained in case the toolkit ever uses it directly, and
--share=ipc is the standard companion to X11 access (MIT-SHM).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A real launch got a display (the x11 fix worked) but then failed twice:

  Failed to read config file '/home/david/.config/monocoque/monocoque.config'
  slog_open_file: Failed to open file: /home/david/.cache/monocoque/...log

The manifest granted no filesystem access at all. monocoque builds these
paths from $HOME directly -- "%s/.config/monocoque/diameters.config" in
monocoque-cli.c -- rather than from XDG_CONFIG_HOME, so it reads and writes
the host's real directories, which the sandbox hid.

Granting the real directories rather than letting XDG redirection give it a
private copy is also what makes a Flatpak monocoque agree with a native one,
and what lets typiql (which reaches the same path, for the same reason)
actually configure it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`monocoque play` inside the sandbox read its config, connected pulseaudio,
and then sat at "setting initial app state" forever with simd running on
the host -- never logging "Opening universal shared memory api". The cause
is that Flatpak gives the sandbox a private /dev/shm: with simd up, `ls
/dev/shm` returned 0 entries inside against 18 on the host, so simapi's
mapping of /dev/shm/SIMAPI.DAT (simmapper.c) found nothing and simd looked
absent.

shm is its own device option -- the --device=all already in this manifest
does not imply it. Re-running the identical command with --device=shm
reached "Opening universal shared memory api" one second in.

Also corrects the --share=network comment: the GraphQL API on port 9000 is
typiql's own backend, not monocoque's.

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

Both artifacts were uninstallable on the distro they are built for, which no
amount of a green CI run shows -- the packages build fine, they just cannot
be installed:

  ubuntu-latest (ubuntu:latest is now 26.04):
    monocoque Depends libconfig9 but none of the choices are installable
  debian-latest (debian:testing, i.e. forky):
    monocoque Depends libxml2 but none of the choices are installable

The names had drifted: libxml2 became libxml2-16 with the soname bump, and
Ubuntu's t64 transition renamed libuv1, libcurl4 and libgtk-3-0. Checked each
name against apt-cache in the same images CI builds in, then verified by
repacking the released .deb with the corrected Depends: it installs on both
Ubuntu 26.04 and Debian forky, and `monocoque --help` runs on Ubuntu.

(On forky it then aborts in monocoquesettingsfree() -- a separate, real bug
in monocoque itself, not a packaging one.)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`monocoque --help` aborted with `free(): invalid pointer` on Debian forky --
found by installing the released debian-latest .deb on the distro it is built
for. gdb put it in monocoquesettingsfree() called straight from main().

The early-exit paths (`--help`, `--version`, bad parameters) jump to
cleanup_final before any field of Parameters or MonocoqueSettings is
assigned, and cleanup_final calls monocoquesettingsfree() and freeparams(),
which free every pointer in both. malloc leaves those pointing at whatever
was in the heap block. It is undefined behaviour everywhere; forky's glibc is
simply the first to check, which is why the same source looked healthy on
Debian stable and both Fedoras.

calloc in all three entry points -- monocoque.c, monocoque-cli.c (the `monocoque`
binary) and monocoque-gui.c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gmonocoque died before drawing a window, in libconfig:

  #0 config_setting_get_member () from libconfig.so.9
  #1 config_setting_lookup ()
  #2 populate_device_list ()

find_default_config() required sim, api and car to all be present on an
entry and skipped the entry if any lookup failed, so it returned -1;
populate_device_list passed that -1 straight to config_setting_get_elem,
got NULL back, and config_setting_lookup dereferenced it.

No real config has all three. conf/monocoque.config -- the example this
project's own README points at -- sets sim and car and never mentions api,
so this crashed for anyone following the documentation. It stayed hidden in
the Flatpak build only until that manifest was given access to the real
~/.config/monocoque; against an empty config there was nothing to look up.

A key that isn't there no longer disqualifies a match, a -1 falls back to
the first entry (which is what a single-entry config means anyway), and both
NULL paths now return with a message instead of dereferencing. Verified by
rebuilding the Flatpak and launching gmonocoque against the real config that
crashed it: the window opens and stays up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
monocoque does nothing on its own: with no daemon publishing
/dev/shm/SIMAPI.DAT, simapi_get_sim() falls through to scanning /proc for the
game itself. Packaging the two together makes a monocoque install usable
rather than half of a working setup.

simapi ships a BUILD_SIMD option in its own CMakeLists, but this tree never
invokes it -- src/monocoque/simulatorapi compiles simapi's sources straight
into a static `simulatorapi` library. The target is declared here over that
same library instead, which also pins the simd in a package to the exact
simapi commit monocoque itself was built against.

Two things the static linking forced, neither of which upstream hits because
it links libsimapi.so:

- simd.c and simapi's getpid.c both define is_pid_running(). Against a shared
  library the executable's definition simply wins; against an archive the
  linker refuses the duplicate. simd's copy is renamed for its own translation
  units with a private -D, so both definitions survive and each caller stays
  bound to the one it was written against -- no patch to the pinned submodule,
  and no --allow-multiple-definition quietly picking a winner.
- a static libyder.a records no dependency on orcania, so every o_malloc and
  split_string it calls comes back undefined. orcania is now named explicitly
  after yder, guarded by find_library so the distros whose shared libyder
  pulls it in on its own are unaffected.

Also installs simapi's example simd.config to share/monocoque: without it simd
still maps telemetry but logs "Disabling Automatic Bridge Mode" and never
launches the Windows bridge under Proton. It can't be installed to $HOME the
way simapi's own build does it, and simd looks for it via getpwuid() rather
than $HOME.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follows the new simd target through every packaging leg.

deb: libyder-dev added to the build deps, libyder2.0t64 to all three control
files (same name on Debian stable, Debian testing and Ubuntu 26.04), and the
binary plus the example simd.config copied into the package.

rpm: Fedora packages neither yder nor orcania -- `dnf search` finds nothing on
43 or 44 -- so both are built from source as *static* libraries and linked
into simd, leaving Requires: satisfiable on a stock system. BUILD_SHARED=OFF
as well as BUILD_STATIC=ON, because a stray libyder.so in the prefix would be
preferred by the linker and put the runtime dependency straight back. The
vendored builds also need -Wno-error via CMAKE_C_FLAGS_RELEASE: both projects
append their own -Wall -Werror to CMAKE_C_FLAGS, and orcania 2.3.3's
strstr()-to-char* assignment is an error on Fedora's GCC. Rehearsing the whole
job in a fedora:44 container caught that, and the undefined orcania symbols
after it, before either cost a CI round.

The spec's %prep also had to stop cloning upstream master unconditionally: the
rpm's contents tracked master rather than the tag being built -- known and
tolerated until now, but simd only exists in this tree, so the package would
have silently shipped without it. It now builds whatever CI stages in
SOURCES, and still clones upstream when nothing is staged so a bare local
`rpmbuild -ba` behaves as before.

AppImage: just the build dep; its AppDir comes from cmake --install, so simd
and the example config arrive on their own.

Flatpak: -DBUILD_SIMD=off, and a header note on why. A sandboxed simd cannot
work -- it identifies sims by scanning /proc for 19 process names and reads
/proc/<pid>/environ for Steam compat vars, while Flatpak gives the sandbox its
own PID namespace with no option to share the host's (measured: 5 pids inside
against 497 out, unchanged by --allow=devel or --filesystem=host). The build
would fail there anyway: no yder in the runtime.

pr-build.yaml gains libyder-dev rather than turning BUILD_SIMD off, so a PR
that breaks simd fails its own checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
%prep no longer clones upstream and pulls submodules itself, so the staged
checkout has to carry them: nappgui_src arrived empty and cmake failed with
"does not contain a CMakeLists.txt file" on both Fedora legs. The local
rehearsal missed it because it mounted a tree whose submodules were already
checked out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Flatpak now ships libsimapi.so and simd alongside monocoque, built from
simapi's own CMakeLists at the commit monocoque's submodule pins, with yder and
orcania as modules beneath them (the freedesktop runtime has neither).

A sandboxed simd is useless on its own, so simapi-flatpak-host-spawn.patch
teaches simapi's process layer to look outside the sandbox. Flatpak gives every
app its own PID namespace and no way to share the host's -- 5 pids visible
inside against 497 out, unchanged by --allow=devel or --filesystem=host -- and
simd finds a sim by scanning /proc for 19 process names, reads
/proc/<pid>/environ for the Steam compat vars behind its auto-bridge, and
launches that bridge under Proton. All of it now routes through `flatpak-spawn
--host`, guarded on /.flatpak-info so native builds keep their original paths
byte for byte.

Four things only running it could have found, each fixed here:

- Output cannot come back through a pipe. popen()ing `flatpak-spawn --host ...`
  hands the write end to the session helper, and a copy outlives every process
  the sandbox can see, so reading to EOF never returns -- simd sat in
  anon_pipe_read with no children left and no further log line. The host now
  writes to $XDG_RUNTIME_DIR/app/$FLATPAK_ID and renames the file into place,
  which makes "the file exists" mean "the output is complete", and the sandbox
  reads an ordinary file.
- Exit statuses are worthless here: simd runs a libuv loop whose SIGCHLD
  handling reaps children these calls never spawned, so system()'s own waitpid
  fails and the status is lost. Every check reads output instead. Before that,
  a detected game read as "no longer detected" one second later, forever.
- Matching `ps -o args=` in full is looser than the libproc2 path it stands in
  for, which compares argv[0]. Observed picking up a shell whose arguments
  merely mentioned AssettoCorsa.exe and tracking that as the game.
- does_file_exist() for Proton ran against the sandbox's filesystem, so wineexe
  stayed NULL, the bridge fork never happened, and nothing was logged above
  debug level. Those checks ask the host now.

Verified end to end against a synthetic sim on the host: detection lands on the
right pid, the environ read returns the Steam vars, the bridge launches on the
host through the portal, /dev/shm/SIMAPI.DAT is published, and killing the game
tears the bridge down again (--watch-bus ties the host process to the wrapper
simd signals).

The manifest also gains --talk-name=org.freedesktop.Flatpak, which the above
depends on and which makes this sandbox largely decorative, and skips the
submodule paths in its `dir` source so a local build works whether or not the
tree has them checked out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only 2 of 24 configured sound devices came up -- "Could not initialize Sound
Device" for the other 22 -- and no shaker fired, on a rig whose PipeWire graph
looked perfect: monocoque streams connected into all six filter chains, every
node running.

sounddev_init() is declared int and fell off its end without returning, so
new_sound_device() read whatever was left in the return register and treated
that as the error code. The PulseAudio streams had already connected by then;
the device objects were simply freed and never fed telemetry again, which is
why the graph looked right and nothing shook.

Undefined behaviour, so it varies by build: the same config drives shakers
from a locally compiled monocoque and not from the packaged one. Now returns
usb_generic_shaker_init()'s own result.

Verified against a live Assetto Corsa session on real hardware: 26 devices
initialised, no failures, shakers working.

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

Two defects in the sandbox escape hatch, both found against a live Assetto
Corsa session rather than the synthetic one it was written with.

`ps -A -o pid=,comm=,args=` cannot be parsed as three fields: comm may contain
spaces, and Wine calls Assetto Corsa's process "AC: main thread". A scanf read
that as comm="AC:" with argv[0]="main", so nothing matched "acs.exe",
simapi_get_sim() never set simstatus, and datacheckcallback's `simstatus >= 2`
gate never opened -- simd detected the game, launched the bridge, and mapped no
telemetry at all. The two fields now come back on separately tagged lines,
which is the same pair libproc2 hands the native path.

And host_capture wrapped the command as `sh -c '<cmd> > out.part'`, so once
<cmd> became a sequence the redirect bound to its last element only and half
the process table went to /dev/null. Grouped with braces now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A .deb declares only what it ships, because its dependencies stay separate
packages carrying their own copyright files. A Flatpak or AppImage distributes
those libraries inside the artifact, so the notices have to travel with it --
and for the LGPL ones here (libconfig, argtable2, libserialport, orcania, yder,
simapi) that also means stating where the corresponding source can be had.
Raised by Paul on the packaging thread; he is right, and neither format was
doing it.

Flatpak: every bundled module copies its own licence out of its source tree at
build time, so the set cannot drift as modules change. lua and glu ship no
licence file at all -- Lua's MIT text lives in doc/readme.html, glu's SGI Free
Software License B only in source headers -- so checked-in copies are installed
instead. nappgui's MIT licence and monocoque's LICENSE.rst come from the tree,
and upstream's own Debian copyright file ships verbatim, since it is the
authoritative account of what is in this source tree (slog MIT, nappgui MIT,
simapi LGPL) rather than anything restated here. THIRD-PARTY-NOTICES.md is
generated from the manifest, so every component's source URL and SHA-256 --
the LGPL "corresponding source" -- is exactly what the build fetched. The
libraries stay dynamically linked in /app/lib, which is the cleanest footing
for relinking.

AppImage: linuxdeploy bundles whatever the binary links from the build host,
which is the larger surface -- GTK, glib, pango and the rest, mostly LGPL.
Rather than research that list, tools/appimage-collect-licenses.sh maps each
bundled library back to its Debian package with dpkg and copies that package's
copyright file into the image, alongside a manifest naming library, package and
version plus the apt-get source line that retrieves the matching source.
linuxdeploy now runs twice, deploying before the collection and packaging
after, since the AppDir has to be populated to be inspected.

Verified: the Flatpak ships notices for all thirteen components, and the
collector was exercised against real distribution libraries, correctly
resolving Debian's t64 package names (libgtk-3-0t64, libglib2.0-0t64,
libuv1t64) and their versions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A workflow_dispatch run built all seven artifacts correctly and then reported
failure on every job, because the release step cannot work without a tag:
"GitHub Releases requires a tag". A build that succeeded should not look
broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Releases are cut from conventional-commit subjects instead of by hand.
Merging the release PR tags the version, writes CHANGELOG.md from the
commits since the last release, and publishes the deb/rpm/AppImage/Flatpak
against that tag.

Configured to fit the 79 tags already in this repo rather than starting a
parallel series: bare semver, no `v` prefix, no component prefix, seeded at
the current 0.3.6. While the major version is 0, a breaking change bumps
the minor rather than jumping to 1.0.0 -- reaching 1.0.0 stays deliberate.

Only feat and fix appear in the notes; refactor, ci, chore, docs and the
rest are recognised but hidden, and can be surfaced later by flipping
`hidden` in release-please-config.json.

ci.yaml becomes callable rather than gaining a second tag trigger. A tag
pushed with the default GITHUB_TOKEN does not start a workflow -- GitHub
blocks that to stop workflows triggering each other -- so `push: tags`
never fires for a release-please tag. Calling ci.yaml from the same run
keeps the release in one place and needs no personal access token stored
on a public repo. The three version-stamping steps and the four release
uploads take the tag from the input when called, and fall back to
github.ref_name for a hand-pushed tag, which keeps working unchanged.

Uploads no longer ask for generated release notes when release-please
supplied them, since that would overwrite the changelog body it just wrote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Per review: simd stays separate so nobody is forced onto one shared-memory
implementation. Removed from every package rather than defaulted off, so
there is no unused build machinery to carry.

Dropped: the CMake simd target and BUILD_SIMD option; simd and simd.config
from the deb, rpm and AppImage; the Flatpak's simapi module, its
orcania/yder dependencies and the host-spawn patch; libyder from the deb
Depends and the CI build deps; and the rpm job's vendored static
orcania/yder build, which existed only because Fedora packages neither and
simd logged through them. monocoque itself links neither.

simapi is untouched -- it remains monocoque's own submodule, compiled into
the static simulatorapi library exactly as upstream's CMakeLists does.

--device=shm stays in the Flatpak and matters more now, not less: it is what
lets a sandboxed monocoque read /dev/shm/SIMAPI.DAT from a simd running on
the host. simapi_get_sim() checks that map before it ever scans /proc, so a
Flatpak monocoque works with a host simd however that simd was installed.

The host-spawn patch is preserved in this branch's history; it belongs with
simd's own packaging, not here.

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

Copy link
Copy Markdown
Owner

are the code changes i just merged still part of this?

is there a way we can separate out simd into it's build as part of the simapi/simd project?

@Spacefreak18

Copy link
Copy Markdown
Owner

like the other change, i might reduce the verbosity of the comments later, especially after I start to understand the new CI better

@Spacefreak18

Copy link
Copy Markdown
Owner

and what i think we can do is make a common downloads page on the simapi.github.io site, as well as publish flathub/apphub whatever

@MrDavid5465

Copy link
Copy Markdown
Contributor Author

are the code changes i just merged still part of this?

is there a way we can separate out simd into it's build as part of the simapi/simd project?

They were bugs that I ran into and fixed when I was testing it as a flatpak and appimage.

Regarding separating that out. I have removed simd from the monocoque packaging. I am working on a pipeline for simapi. It builds I just haven't tested the release artifacts directly yet. It should work, since I already got it working while bundled.

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