From f9f4c7a040e0e32046e3e57c42381812af2f8420 Mon Sep 17 00:00:00 2001 From: FA Saikat Date: Wed, 14 Oct 2026 23:01:16 +0600 Subject: [PATCH 1/3] Add Debian package build and release workflow packaging/build-deb.sh builds a reproducible .deb from a pinned commit (qmake6 PREFIX=/usr SYSCONFDIR=/etc, INSTALL_ROOT staging, dpkg-shlibdeps Depends, dpkg-deb). .github/workflows/deb.yml builds it on Debian trixie for packaging PRs and attaches the .deb to published releases, next to the AppImages. Follows up on #191. --- .github/workflows/deb.yml | 68 ++++++++++++++ .gitignore | 7 ++ packaging/build-deb.sh | 191 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 .github/workflows/deb.yml create mode 100755 packaging/build-deb.sh diff --git a/.github/workflows/deb.yml b/.github/workflows/deb.yml new file mode 100644 index 00000000..a4234442 --- /dev/null +++ b/.github/workflows/deb.yml @@ -0,0 +1,68 @@ +name: Debian package + +# Builds a Skyscraper .deb via packaging/build-deb.sh on Debian trixie +# (Qt6; avoids libc mismatch with current Debian) and attaches it to +# published releases, next to the AppImages. +# +# Version numbering and shipping stay manual: this only reacts to releases +# and to changes under packaging/ (plus manual dispatch for testing). +# +# For a new upstream version: bump PINNED_VERSION/PINNED_COMMIT in +# packaging/build-deb.sh first (CI always builds the exact triggering +# commit via --pin-commit). + +on: + release: + types: [published] + pull_request: + paths: ['packaging/**', '.github/workflows/deb.yml'] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-24.04 + container: + image: debian:trixie + steps: + # debian:trixie ships no git, so this falls back to a REST tarball. + - name: Checkout sources + uses: actions/checkout@v4 + - name: Install build dependencies + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates git g++ make dpkg-dev \ + qmake6 qt6-base-dev qt6-base-dev-tools libqt6sql6-sqlite p7zip-full + # Now that git exists, check out again for a real repository. + - name: Checkout sources with git + uses: actions/checkout@v4 + - name: Build .deb + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + ./packaging/build-deb.sh --out-dir dist --pin-commit "$GITHUB_SHA" + - name: Upload .deb artifact + uses: actions/upload-artifact@v4 + with: + name: skyscraper-deb + path: dist/*.deb + if-no-files-found: error + + attach: + if: github.event_name == 'release' + needs: build + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Download .deb artifact + uses: actions/download-artifact@v4 + with: + name: skyscraper-deb + path: dist + - name: Attach .deb to release + uses: softprops/action-gh-release@v3 + with: + files: dist/*.deb diff --git a/.gitignore b/.gitignore index 599bbe55..ecea4a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,10 @@ platforms_idmap_local.csv # docker config docker/config.ini + +# Debian packaging artifacts (see packaging/build-deb.sh) +*.deb +# ...but keep the build script itself (matched by *build-* above) +!packaging/build-deb.sh +# Local scratch for packaging builds (build tree, staging dir) +.tmp/ diff --git a/packaging/build-deb.sh b/packaging/build-deb.sh new file mode 100755 index 00000000..e8331718 --- /dev/null +++ b/packaging/build-deb.sh @@ -0,0 +1,191 @@ +#!/usr/bin/env bash +# +# Build a Skyscraper .deb for Debian-based systems. +# +# Recipe (per docs, RetroPie scriptmodule, Debian trixie host): +# pinned source -> qmake6 (PREFIX=/usr, SYSCONFDIR=/etc) -> make -> +# make install INSTALL_ROOT= -> dpkg-shlibdeps Depends -> +# dpkg-deb --build +# +# Usage: +# packaging/build-deb.sh [--out-dir DIR] [--keep-tmp] [--pin-commit SHA] +# +# Output: +# skyscraper_-1_amd64.deb (default: repo root) +# +# Notes: +# - Builds from a clean `git archive HEAD` export, so the working tree stays +# clean. HEAD must match PINNED_COMMIT (or the SHA passed via --pin-commit). +# - All temporary work (build tree, staging dir) lives under $REPO_ROOT/.tmp +# (git-ignored). Nothing is written outside the repo. +# - SYSCONFDIR=/etc is a deliberate FHS choice: upstream default would be +# /usr/etc with PREFIX=/usr. The binary probes SYSCONFDIR/skyscraper at +# runtime and falls back to single-binary mode when absent. +# - libqt6sql6-sqlite and p7zip-full are runtime-only deps (dlopen/exec) that +# dpkg-shlibdeps cannot see; they are appended to Depends manually. +# - No skyscraper.cfg / config.ini is shipped: the app seeds per-user config +# from /etc/skyscraper examples on first run, falling back to built-in +# defaults. + +set -euo pipefail + +PINNED_VERSION="3.21.1" +PINNED_COMMIT="ccd49ae99a13ce037476f1d3cc3b646afca2059e" +PKG_NAME="skyscraper" +PKG_REVISION="1" +ARCH="amd64" +# Neutral default; downstream forks override with DEB_MAINTAINER. +MAINTAINER="${DEB_MAINTAINER:-Gemba }" + +OUT_DIR="" +KEEP_TMP=0 +PIN_COMMIT="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --out-dir) + OUT_DIR="$2" + shift 2 + ;; + --pin-commit) + PIN_COMMIT="$2" + shift 2 + ;; + --keep-tmp) + KEEP_TMP=1 + shift + ;; + -h|--help) + sed -n '2,/^$/p' "$0" + echo "Usage: $0 [--out-dir DIR] [--keep-tmp] [--pin-commit SHA]" + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + exit 1 + ;; + esac +done + +REPO_ROOT="$(git rev-parse --show-toplevel)" +cd "$REPO_ROOT" + +if [[ -z "$OUT_DIR" ]]; then + OUT_DIR="$REPO_ROOT" +fi +mkdir -p "$OUT_DIR" + +# --- Pin check --------------------------------------------------------------- +# Default pin: last locally validated source. CI / releases pass the exact +# commit explicitly via --pin-commit (a hardcoded pin can never survive the +# commit that carries it, so the override is the release mechanism). +HEAD_COMMIT="$(git rev-parse HEAD)" +WANT_COMMIT="${PIN_COMMIT:-$PINNED_COMMIT}" +if [[ "$HEAD_COMMIT" != "$WANT_COMMIT" ]]; then + echo "ERROR: HEAD ($HEAD_COMMIT) is not the expected commit ($WANT_COMMIT, v$PINNED_VERSION)." >&2 + echo "Check out the expected commit, or pass --pin-commit to build it deliberately." >&2 + exit 1 +fi + +TREE_VERSION="$(grep -oE '[0-9]+\.[0-9]+\.[0-9]+' VERSION.ini | head -n 1)" +if [[ "$TREE_VERSION" != "$PINNED_VERSION" ]]; then + echo "ERROR: VERSION.ini says $TREE_VERSION, expected $PINNED_VERSION." >&2 + exit 1 +fi + +# --- Dependency check ---------------------------------------------------------- +for tool in qmake6 make g++ dpkg-shlibdeps dpkg-deb; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "ERROR: required tool '$tool' not found." >&2 + echo "Install build deps: sudo apt-get install qt6-base-dev qmake6 qt6-base-dev-tools libqt6sql6-sqlite p7zip-full" >&2 + exit 1 + fi +done + +DEB_VERSION="${PINNED_VERSION}-${PKG_REVISION}" +DEB_FILE="${PKG_NAME}_${DEB_VERSION}_${ARCH}.deb" + +TMP_BASE="$REPO_ROOT/.tmp/build-deb" +mkdir -p "$TMP_BASE" +BUILD_DIR="$(mktemp -d "$TMP_BASE/build-XXXXXX")" +STAGING_DIR="$(mktemp -d "$TMP_BASE/staging-XXXXXX")" + +cleanup() { + if [[ "$KEEP_TMP" -eq 0 ]]; then + rm -rf "$BUILD_DIR" "$STAGING_DIR" 2>/dev/null || true + else + echo "Kept temp dirs: $BUILD_DIR $STAGING_DIR" + fi + # Remove the shlibdeps stub if we created it inside the export. + rm -rf "$BUILD_DIR/debian" 2>/dev/null || true +} +trap cleanup EXIT + +echo "==> Exporting clean tree of $HEAD_COMMIT to $BUILD_DIR" +git archive HEAD | tar -x -C "$BUILD_DIR" + +echo "==> Building (qmake6 PREFIX=/usr SYSCONFDIR=/etc && make)" +pushd "$BUILD_DIR" >/dev/null +rm -f .qmake.stash +PREFIX=/usr SYSCONFDIR=/etc qmake6 +make -j"$(nproc)" + +echo "==> Staging install to $STAGING_DIR" +make install INSTALL_ROOT="$STAGING_DIR" +popd >/dev/null + +if [[ ! -x "$STAGING_DIR/usr/bin/Skyscraper" ]]; then + echo "ERROR: staged binary missing: $STAGING_DIR/usr/bin/Skyscraper" >&2 + exit 1 +fi +if [[ ! -f "$STAGING_DIR/etc/skyscraper/peas.json" ]]; then + echo "ERROR: staged data files missing under $STAGING_DIR/etc/skyscraper" >&2 + exit 1 +fi + +echo "==> Computing Depends via dpkg-shlibdeps" +# dpkg-shlibdeps requires a debian/control stub in the analysis directory. +mkdir -p "$BUILD_DIR/debian" +printf 'Source: %s\nSection: games\nPriority: optional\n\nPackage: %s\nArchitecture: %s\nDepends: ${shlibs:Depends}\nDescription: stub\n' \ + "$PKG_NAME" "$PKG_NAME" "$ARCH" >"$BUILD_DIR/debian/control" +SHLIBS_DEPENDS="$(cd "$BUILD_DIR" && dpkg-shlibdeps -O "$STAGING_DIR/usr/bin/Skyscraper" 2>/dev/null | sed 's/^shlibs:Depends=//')" +rm -rf "$BUILD_DIR/debian" +if [[ -z "$SHLIBS_DEPENDS" ]]; then + echo "ERROR: dpkg-shlibdeps produced empty Depends." >&2 + exit 1 +fi +# Runtime-only deps invisible to shlibdeps: Qt SQLite driver (dlopened) and +# p7zip-full (shelled out to for archive handling). +DEPENDS="${SHLIBS_DEPENDS}, libqt6sql6-sqlite, p7zip-full" +echo " Depends: $DEPENDS" + +echo "==> Writing DEBIAN/control" +mkdir -p "$STAGING_DIR/DEBIAN" +cat >"$STAGING_DIR/DEBIAN/control" < Building $DEB_FILE" +dpkg-deb --build "$STAGING_DIR" "$OUT_DIR/$DEB_FILE" + +echo "==> Verifying" +dpkg-deb --field "$OUT_DIR/$DEB_FILE" +echo "--- contents (first 45 entries) ---" +# head closes the pipe early (SIGPIPE); without this, `set -o pipefail` +# would fail the build on a successful listing. +dpkg-deb --contents "$OUT_DIR/$DEB_FILE" | head -n 45 || true + +echo "OK: $OUT_DIR/$DEB_FILE" From ddf855a724f7f22dba0e5948d676ad4728cf4eaf Mon Sep 17 00:00:00 2001 From: Gemba Date: Sun, 18 Oct 2026 11:03:11 +0200 Subject: [PATCH 2/3] Update and cleanup .gitignore --- .gitignore | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index ecea4a3c..b978998c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,21 +14,18 @@ object_script.*.Release object_script.*.Debug *_plugin_import.cpp -/.qmake.cache -/.qmake.stash +.qmake* *.pro.user *.pro.user.* *.qbs.user *.qbs.user.* *.moc -moc_*.cpp -moc_*.h +moc_* qrc_*.cpp ui_*.h *.qmlc *.jsc Makefile* -*build-* *.qm *.prl @@ -45,19 +42,14 @@ target_wrapper.* # QtCreator CMake CMakeLists.txt.user* -# QtCreator 4.8< compilation database -compile_commands.json - # QtCreator local machine specific files for imported projects *creator.user* *_qmlcache.qrc -moc_* *.tmp *.bkp *.bk -.qmake* Skyscraper Skyscraper.app/**/* packages @@ -77,9 +69,5 @@ platforms_idmap_local.csv # docker config docker/config.ini -# Debian packaging artifacts (see packaging/build-deb.sh) +# Debian packaging artifacts (see build-deb.sh) *.deb -# ...but keep the build script itself (matched by *build-* above) -!packaging/build-deb.sh -# Local scratch for packaging builds (build tree, staging dir) -.tmp/ From 1a18b0741bbcee700cf1507d513a1a580f5d41d6 Mon Sep 17 00:00:00 2001 From: Gemba Date: Sun, 18 Oct 2026 11:05:53 +0200 Subject: [PATCH 3/3] adjustments and remove surplus stuff --- .github/workflows/deb.yml | 13 ++++--------- packaging/build-deb.sh | 26 +++++++++++--------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/.github/workflows/deb.yml b/.github/workflows/deb.yml index a4234442..b712891a 100644 --- a/.github/workflows/deb.yml +++ b/.github/workflows/deb.yml @@ -15,7 +15,8 @@ on: release: types: [published] pull_request: - paths: ['packaging/**', '.github/workflows/deb.yml'] + # FIXME: change path packaging -> supplementary/workflows + paths: ['packaging/build-deb.sh', '.github/workflows/deb.yml'] workflow_dispatch: permissions: @@ -27,21 +28,17 @@ jobs: container: image: debian:trixie steps: - # debian:trixie ships no git, so this falls back to a REST tarball. - - name: Checkout sources - uses: actions/checkout@v4 - name: Install build dependencies run: | apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ ca-certificates git g++ make dpkg-dev \ - qmake6 qt6-base-dev qt6-base-dev-tools libqt6sql6-sqlite p7zip-full - # Now that git exists, check out again for a real repository. + qmake6 qt6-base-dev qt6-base-dev-tools \ + libqt6sql6-sqlite p7zip-full - name: Checkout sources with git uses: actions/checkout@v4 - name: Build .deb run: | - git config --global --add safe.directory "$GITHUB_WORKSPACE" ./packaging/build-deb.sh --out-dir dist --pin-commit "$GITHUB_SHA" - name: Upload .deb artifact uses: actions/upload-artifact@v4 @@ -54,8 +51,6 @@ jobs: if: github.event_name == 'release' needs: build runs-on: ubuntu-24.04 - permissions: - contents: write steps: - name: Download .deb artifact uses: actions/download-artifact@v4 diff --git a/packaging/build-deb.sh b/packaging/build-deb.sh index e8331718..5041861c 100755 --- a/packaging/build-deb.sh +++ b/packaging/build-deb.sh @@ -29,7 +29,6 @@ set -euo pipefail -PINNED_VERSION="3.21.1" PINNED_COMMIT="ccd49ae99a13ce037476f1d3cc3b646afca2059e" PKG_NAME="skyscraper" PKG_REVISION="1" @@ -80,19 +79,16 @@ mkdir -p "$OUT_DIR" # commit explicitly via --pin-commit (a hardcoded pin can never survive the # commit that carries it, so the override is the release mechanism). HEAD_COMMIT="$(git rev-parse HEAD)" -WANT_COMMIT="${PIN_COMMIT:-$PINNED_COMMIT}" +# expand potential shortened commit +WANT_COMMIT="$(git rev-parse ${PIN_COMMIT:-$PINNED_COMMIT})" if [[ "$HEAD_COMMIT" != "$WANT_COMMIT" ]]; then - echo "ERROR: HEAD ($HEAD_COMMIT) is not the expected commit ($WANT_COMMIT, v$PINNED_VERSION)." >&2 + echo "ERROR: HEAD (${HEAD_COMMIT:0:8}) is not the expected commit (${WANT_COMMIT:0:8})." >&2 echo "Check out the expected commit, or pass --pin-commit to build it deliberately." >&2 exit 1 fi -TREE_VERSION="$(grep -oE '[0-9]+\.[0-9]+\.[0-9]+' VERSION.ini | head -n 1)" -if [[ "$TREE_VERSION" != "$PINNED_VERSION" ]]; then - echo "ERROR: VERSION.ini says $TREE_VERSION, expected $PINNED_VERSION." >&2 - exit 1 -fi - +TREE_VERSION="$(sed -E 's/VERSION="(.+)"/\1/' VERSION.ini | head -n 1)" +TREE_VERSION="$(echo "$TREE_VERSION" | sed -E "s/-dev/-$(git describe --always)/")" # --- Dependency check ---------------------------------------------------------- for tool in qmake6 make g++ dpkg-shlibdeps dpkg-deb; do if ! command -v "$tool" >/dev/null 2>&1; then @@ -102,10 +98,10 @@ for tool in qmake6 make g++ dpkg-shlibdeps dpkg-deb; do fi done -DEB_VERSION="${PINNED_VERSION}-${PKG_REVISION}" +DEB_VERSION="${TREE_VERSION}-${PKG_REVISION}" DEB_FILE="${PKG_NAME}_${DEB_VERSION}_${ARCH}.deb" -TMP_BASE="$REPO_ROOT/.tmp/build-deb" +TMP_BASE="$REPO_ROOT/build/debpkg" mkdir -p "$TMP_BASE" BUILD_DIR="$(mktemp -d "$TMP_BASE/build-XXXXXX")" STAGING_DIR="$(mktemp -d "$TMP_BASE/staging-XXXXXX")" @@ -124,9 +120,9 @@ trap cleanup EXIT echo "==> Exporting clean tree of $HEAD_COMMIT to $BUILD_DIR" git archive HEAD | tar -x -C "$BUILD_DIR" -echo "==> Building (qmake6 PREFIX=/usr SYSCONFDIR=/etc && make)" +echo "==> Building Skyscraper v$TREE_VERSION" pushd "$BUILD_DIR" >/dev/null -rm -f .qmake.stash +make --ignore-errors distclean 2>/dev/null || true PREFIX=/usr SYSCONFDIR=/etc qmake6 make -j"$(nproc)" @@ -183,9 +179,9 @@ dpkg-deb --build "$STAGING_DIR" "$OUT_DIR/$DEB_FILE" echo "==> Verifying" dpkg-deb --field "$OUT_DIR/$DEB_FILE" -echo "--- contents (first 45 entries) ---" +echo "--- contents ---" # head closes the pipe early (SIGPIPE); without this, `set -o pipefail` # would fail the build on a successful listing. -dpkg-deb --contents "$OUT_DIR/$DEB_FILE" | head -n 45 || true +dpkg-deb --contents "$OUT_DIR/$DEB_FILE" || true echo "OK: $OUT_DIR/$DEB_FILE"