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

This commit is contained in:
David Scott
2026-09-10 14:34:56 -04:00
committed by GitHub
parent cd67cb1689
commit 45f2ef7fef
37 changed files with 1199 additions and 82 deletions
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# Collect licence and copyright information for everything an AppImage bundles.
#
# usage: tools/appimage-collect-licenses.sh <AppDir>
#
# A .deb declares only what it ships, because its dependencies remain separate
# packages carrying their own copyright files. An AppImage carries those
# libraries inside it, so their notices have to travel with it -- and most of
# them here are LGPL (GTK, glib, pango and friends), where the obligation also
# covers saying where the corresponding source can be had.
#
# The mapping is mechanical rather than researched: every bundled library came
# from a Debian/Ubuntu package on the build host, so dpkg can name it, and the
# archive already holds a copyright file for each. Run this after linuxdeploy
# has populated the AppDir and before it packages the image.
set -euo pipefail
APPDIR="${1:?AppDir path}"
DOCDIR="$APPDIR/usr/share/doc"
MANIFEST="$DOCDIR/BUNDLED-LIBRARIES.txt"
mkdir -p "$DOCDIR"
{
echo "Libraries bundled in this AppImage"
echo "=================================="
echo
echo "Each library below was taken from the Debian/Ubuntu package named"
echo "beside it on the build host. That package's copyright file is included"
echo "under usr/share/doc/<package>/copyright in this image."
echo
echo "The corresponding source for any of them can be obtained with:"
echo " apt-get source <package>=<version>"
echo "from the distribution and release recorded below."
echo
echo "Build host: $( (. /etc/os-release && echo "$PRETTY_NAME") 2>/dev/null || echo unknown)"
echo
printf '%-44s %-34s %s\n' "LIBRARY" "PACKAGE" "VERSION"
} > "$MANIFEST"
found=0
unowned=0
while IFS= read -r so; do
base="$(basename "$so")"
pkg=""
# Try the usual multiarch location first, then fall back to a name search.
for candidate in "/usr/lib/x86_64-linux-gnu/$base" "/lib/x86_64-linux-gnu/$base" "/usr/lib/$base"; do
pkg="$(dpkg -S "$candidate" 2>/dev/null | head -1 | cut -d: -f1 || true)"
[ -n "$pkg" ] && break
done
if [ -z "$pkg" ]; then
pkg="$(dpkg -S "$base" 2>/dev/null | head -1 | cut -d: -f1 || true)"
fi
if [ -n "$pkg" ]; then
ver="$(dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null || echo unknown)"
printf '%-44s %-34s %s\n' "$base" "$pkg" "$ver" >> "$MANIFEST"
if [ -f "/usr/share/doc/$pkg/copyright" ] && [ ! -f "$DOCDIR/$pkg/copyright" ]; then
install -Dm644 "/usr/share/doc/$pkg/copyright" "$DOCDIR/$pkg/copyright"
fi
found=$((found + 1))
else
# Built by this job rather than installed from a package -- its licence is
# collected separately, from its own source tree.
printf '%-44s %-34s %s\n' "$base" "(built from source in this job)" "-" >> "$MANIFEST"
unowned=$((unowned + 1))
fi
done < <(find "$APPDIR" -name '*.so*' -type f | sort)
echo >> "$MANIFEST"
echo "$found libraries from distribution packages, $unowned built in this job." >> "$MANIFEST"
echo "collected copyright for $found bundled libraries ($unowned built here)"
echo "manifest: $MANIFEST"
@@ -5,4 +5,4 @@ Priority: optional
Architecture: amd64
Maintainer: Paul Jones <paul@spacefreak18.xyz>
Description: Racing Simulator Device Manager
Depends: libpulse0,libconfig11,libargtable2-0,liblua5.4-0,libuv1t64,libserialport0,libxml2,libxdg-basedir1,libhidapi-hidraw0,libgtk-3-0t64,libcurl4t64,libglu1-mesa
Depends: libpulse0,libconfig11,libargtable2-0,liblua5.4-0,libuv1t64,libserialport0,libxml2-16,libxdg-basedir1,libhidapi-hidraw0,libgtk-3-0t64,libcurl4t64,libglu1-mesa
@@ -5,4 +5,4 @@ Priority: optional
Architecture: amd64
Maintainer: Paul Jones <paul@spacefreak18.xyz>
Description: Racing Simulator Device Manager
Depends: libpulse0,libconfig9,libargtable2-0,liblua5.4-0,libuv1,libserialport0,libxml2,libxdg-basedir1,libhidapi-hidraw0,libglu1-mesa,libcurl4,libgtk-3-0
Depends: libpulse0,libconfig11,libargtable2-0,liblua5.4-0,libuv1t64,libserialport0,libxml2-16,libxdg-basedir1,libhidapi-hidraw0,libglu1-mesa,libcurl4t64,libgtk-3-0t64
+13 -6
View File
@@ -15,14 +15,21 @@ Requires: pulseaudio-libs argtable libconfig hidapi libserialport libuv libxdg-b
%description
A device manager for Racing sims
# Builds whatever tree has been staged at %{_sourcedir}/monocoque, cloning it
# from upstream master only if nothing is staged. The unconditional clone this
# replaced meant an rpm's contents tracked master rather than the tag being
# built -- so a fix on the branch being released was absent from its own
# release. CI stages the checked-out tree; a bare `rpmbuild -ba` on a
# workstation still works exactly as before.
%prep
rm -rf $RPM_BUILD_DIR/monocoque
rm -rf $RPM_SOURCE_DIR/monocoque
cd $RPM_SOURCE_DIR
git clone https://github.com/spacefreak18/monocoque
cd monocoque
git submodule update --init --recursive
cd ..
if [ ! -d $RPM_SOURCE_DIR/monocoque ]; then
cd $RPM_SOURCE_DIR
git clone https://github.com/spacefreak18/monocoque
cd monocoque
git submodule update --init --recursive
cd ..
fi
cp -r $RPM_SOURCE_DIR/monocoque $RPM_BUILD_DIR/
%build
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Generate a third-party notices file from the Flatpak manifest.
A .deb lists only what it actually ships, because its dependencies are separate
packages carrying their own copyright files. A Flatpak or AppImage distributes
those libraries itself, so the notices have to travel with the artifact -- and
for the LGPL ones (libconfig, argtable2, libserialport, orcania, yder) that
includes saying where the corresponding source can be obtained.
The manifest already pins every dependency by URL and sha256, which is exactly
that information, so this is generated from it rather than maintained by hand
where it would drift.
"""
import re
import sys
from pathlib import Path
manifest = Path(sys.argv[1] if len(sys.argv) > 1
else "flatpak/io.github.spacefreak18.monocoque.yml")
text = manifest.read_text()
entries, name = [], None
for line in text.splitlines():
m = re.match(r"^ - name: (\S+)", line)
if m:
name = m.group(1)
entries.append({"name": name, "url": None, "sha256": None, "commit": None, "tag": None})
elif entries:
for key in ("url", "sha256", "commit", "tag"):
k = re.match(rf"^\s+{key}: (\S+)", line)
if k and entries[-1][key] is None:
entries[-1][key] = k.group(1)
out = [
"# Third-party notices",
"",
"This package bundles the libraries below. Each one's own licence text is",
"installed alongside it under `/app/share/licenses/<name>/`.",
"",
"Where a bundled library is under the LGPL, the corresponding source is the",
"exact archive listed here, identified by URL and SHA-256; the same version",
"can be rebuilt from it, and the Flatpak links these libraries dynamically.",
"",
"| Component | Source | Checksum / revision |",
"|---|---|---|",
]
for e in entries:
if not e["url"]:
continue
rev = e["sha256"] or e["commit"] or e["tag"] or ""
out.append(f"| {e['name']} | {e['url']} | `{rev[:16]}{'…' if len(rev) > 16 else ''}` |")
out += [
"",
"Generated by tools/generate-third-party-notices.py from the Flatpak",
"manifest -- do not edit by hand.",
"",
]
print("\n".join(out))
+1 -1
View File
@@ -3,7 +3,7 @@ Version=1.0
Type=Application
Name=Monocoque
Comment=Launch Monocoque
Exec=/usr/bin/gmonocoque
Exec=gmonocoque
Icon=monocoque
Terminal=false
Categories=Game;Development;
+49
View File
@@ -0,0 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100%" height="100%">
<!-- Square Flatpak icon canvas wrapping the real monocoque.svg artwork
(fetched from https://repo.spacefreak18.xyz/monocoque/icons/monocoque.svg,
viewBox 0 0 100 50), vertically centered via translate(0,25) rather
than stretched. Flatpak's export step requires square icons and
appstreamcli additionally requires any desktop-application to have
one at all, confirmed via two failed real CI runs. -->
<g transform="translate(0,25)">
<!-- Main Body / Chassis -->
<path d="M 25 28 C 35 28, 40 22, 55 22 L 72 24 C 76 24, 80 28, 85 28 L 85 34 L 25 34 Z" fill="#E10600" />
<!-- Nose Cone -->
<path d="M 25 28 L 12 30 C 10 30.5, 10 32.5, 12 33 L 25 34 Z" fill="#C00500" />
<!-- Front Wing -->
<rect x="5" y="31" width="10" height="4" rx="1" fill="#1A1A1A" />
<path d="M 12 31 L 12 35" stroke="#FFFFFF" stroke-width="1" />
<!-- Rear Wing -->
<path d="M 82 28 L 84 15 L 92 15 L 90 28 Z" fill="#1A1A1A" />
<rect x="80" y="13" width="14" height="4" rx="0.5" fill="#1A1A1A" />
<!-- Engine Cover / Airbox Intake -->
<path d="M 58 22 C 58 16, 64 16, 66 16 L 70 24 Z" fill="#E10600" />
<ellipse cx="61" cy="19" rx="2" ry="1.5" fill="#1A1A1A" />
<!-- Cockpit / Driver Helmet -->
<path d="M 48 23 C 48 20, 54 20, 54 23 Z" fill="#1A1A1A" />
<circle cx="52" cy="19" r="2.5" fill="#FFFFFF" />
<path d="M 51.5 17.5 L 54.5 18.5 L 53.5 20.5 L 51.5 19.5 Z" fill="#1A1A1A" /> <!-- Visor -->
<!-- Bargeboard / Sidepod Detail -->
<path d="M 38 27 L 55 25 L 58 34 L 36 34 Z" fill="#B00400" />
<!-- Wheels (Tires & Rims) -->
<!-- Rear Wheel -->
<circle cx="78" cy="32" r="11" fill="#1A1A1A" />
<circle cx="78" cy="32" r="5" fill="#4A4A4A" />
<circle cx="78" cy="32" r="2" fill="#D4AF37" />
<!-- Front Wheel -->
<circle cx="22" cy="33" r="9" fill="#1A1A1A" />
<circle cx="22" cy="33" r="4.2" fill="#4A4A4A" />
<circle cx="22" cy="33" r="1.8" fill="#D4AF37" />
<!-- Ground Shadow -->
<ellipse cx="50" cy="44" rx="42" ry="2" fill="#000000" fill-opacity="0.2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB