Skip to content

deb packaging

deb packaging #45

Workflow file for this run

name: build
on:
push:
branches: ["**"]
tags: ["v*"]
pull_request:
workflow_dispatch:
permissions:
contents: write
env:
LIBTCD_URL: https://flaterco.com/files/xtide/libtcd-2.2.7-r3.tar.xz
HARMONICS_URL: https://flaterco.com/files/xtide/harmonics-dwf-20251228-free.tar.xz
XTIDE_WIN64_ZIP_URL: https://flaterco.com/files/xtide/xtide-2.15-win64.zip
jobs:
linux:
name: linux (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runs_on: ubuntu-latest
tar_suffix: linux-amd64
- arch: arm64
runs_on: ubuntu-24.04-arm
tar_suffix: linux-arm64
runs-on: ${{ matrix.runs_on }}
steps:
- uses: actions/checkout@v4
- name: Compute artifact tag + deb version
run: |
set -euo pipefail
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "ARTIFACT_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
echo "DEB_VERSION=${GITHUB_REF_NAME#v}-1" >> "$GITHUB_ENV"
else
echo "ARTIFACT_TAG=sha-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
echo "DEB_VERSION=0.0.0+sha${GITHUB_SHA::7}-1" >> "$GITHUB_ENV"
fi
echo "DEB_ARCH=$(dpkg --print-architecture)" >> "$GITHUB_ENV"
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y \
cmake ninja-build pkg-config g++ \
curl tar xz-utils make \
dpkg-dev fakeroot apt-utils patchelf
- name: Build + install libtcd (prefix)
run: |
set -euo pipefail
rm -rf _deps && mkdir -p _deps
cd _deps
curl -L --retry 3 --retry-delay 2 -o libtcd.tar.xz "${LIBTCD_URL}"
tar -xf libtcd.tar.xz
LIBTCD_DIR="$(find . -maxdepth 1 -type d -name 'libtcd-*' | head -n1)"
PREFIX="$PWD/libtcd-prefix"
cd "$LIBTCD_DIR"
./configure --prefix="$PREFIX"
make -j
make install
echo "TCD_PREFIX=$PREFIX" >> "$GITHUB_ENV"
- name: Configure (release)
run: |
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${{ env.TCD_PREFIX }}"
- name: Build
run: |
cmake --build build -j
- name: Download harmonics (.tcd) for smoke test
run: |
set -euo pipefail
rm -rf _deps/harmonics
mkdir -p _deps/harmonics
cd _deps/harmonics
curl -L --retry 3 --retry-delay 2 -o harmonics.tar.xz "${HARMONICS_URL}"
tar -xf harmonics.tar.xz
TCD_FILE="$(find . -type f -name '*.tcd' | head -n1)"
if [ -z "${TCD_FILE}" ]; then
echo "ERROR: No .tcd found after extracting harmonics archive"
find . -maxdepth 3 -type f | head -n 50 || true
exit 1
fi
echo "HARM_TCD=$PWD/${TCD_FILE#./}" >> "$GITHUB_ENV"
echo "Using HARM_TCD=$PWD/${TCD_FILE#./}"
- name: Smoke test (Linux)
run: |
set -euo pipefail
LAT="40.7050"
LON="-74.0120"
echo "=== xtide-nearest --help ==="
./build/xtide-nearest --help | head -n 60 || true
echo ""
echo "=== Nearest TIDE station (lat=${LAT}, lon=${LON}) ==="
./build/xtide-nearest --lat "${LAT}" --lon "${LON}" --tcd "${{ env.HARM_TCD }}" \
nearest --no-current --top 1
echo ""
echo "=== Nearest CURRENT station (lat=${LAT}, lon=${LON}) ==="
./build/xtide-nearest --lat "${LAT}" --lon "${LON}" --tcd "${{ env.HARM_TCD }}" \
nearest --no-tide --top 1
- name: Package (tar.gz)
run: |
set -euo pipefail
rm -rf dist
mkdir -p dist
cp -f build/xtide-nearest dist/
[ -f README.md ] && cp -f README.md dist/ || true
[ -f LICENSE ] && cp -f LICENSE dist/ || true
tar -czf xtide-nearest-${{ env.ARTIFACT_TAG }}-${{ matrix.tar_suffix }}.tar.gz -C dist .
- name: Package (Debian .deb, bundles libtcd privately)
run: |
set -euo pipefail
PKG="xtide-nearest"
INSTALL_ROOT="/usr/lib/${PKG}"
STAGE="debian/${PKG}"
rm -rf debian
install -d \
"$STAGE/DEBIAN" \
"$STAGE/usr/bin" \
"$STAGE${INSTALL_ROOT}/lib" \
"$STAGE/usr/share/doc/$PKG"
# App under private prefix
install -m 0755 build/xtide-nearest "$STAGE${INSTALL_ROOT}/xtide-nearest"
# Bundle libtcd into private prefix (no global /usr/lib install => no conflicts)
if [ -e "${{ env.TCD_PREFIX }}/lib/libtcd.so.1" ]; then
cp -a "${{ env.TCD_PREFIX }}/lib/libtcd.so.1"* "$STAGE${INSTALL_ROOT}/lib/"
elif ls "${{ env.TCD_PREFIX }}/lib/libtcd.so"* >/dev/null 2>&1; then
cp -a "${{ env.TCD_PREFIX }}/lib/libtcd.so"* "$STAGE${INSTALL_ROOT}/lib/"
else
echo "ERROR: libtcd shared library not found under ${{ env.TCD_PREFIX }}/lib"
ls -la "${{ env.TCD_PREFIX }}/lib" || true
exit 1
fi
# Correct RPATH for this layout:
# binary: .../usr/lib/xtide-nearest/xtide-nearest
# libs: .../usr/lib/xtide-nearest/lib/libtcd.so.1
# => $ORIGIN/lib
patchelf --set-rpath '$ORIGIN/lib' "$STAGE${INSTALL_ROOT}/xtide-nearest"
# /usr/bin wrapper
cat > "$STAGE/usr/bin/$PKG" <<'EOF'
#!/bin/sh
set -e
: "${XTIDE_NEAREST_HOME:=/usr/lib/xtide-nearest}"
exec "$XTIDE_NEAREST_HOME/xtide-nearest" "$@"
EOF
chmod 0755 "$STAGE/usr/bin/$PKG"
# Optional docs
[ -f README.md ] && install -m 0644 README.md "$STAGE/usr/share/doc/$PKG/README.md" || true
[ -f LICENSE ] && install -m 0644 LICENSE "$STAGE/usr/share/doc/$PKG/copyright" || true
SIZE_KB="$(du -sk "$STAGE/usr" | awk '{print $1}')"
# Minimal debian/control so dpkg-shlibdeps can compute system Depends
cat > debian/control <<EOF
Source: $PKG
Section: utils
Priority: optional
Maintainer: ${GITHUB_REPOSITORY} <noreply@github.com>
Standards-Version: 4.6.2
Package: $PKG
Architecture: any
Depends: \${shlibs:Depends}, \${misc:Depends}
Description: xtide-nearest command-line tool
Find nearest tide/current station from an XTide harmonics database.
EOF
# Compute Depends from the staged binary.
# -l points dpkg-shlibdeps at the private in-package lib dir so libtcd is treated as "provided"
# System libs (libstdc++6, libgcc-s1, libc6, etc.) stay as Depends.
DEPENDS="$(dpkg-shlibdeps --ignore-missing-info -O -p"$PKG" \
-l"$STAGE${INSTALL_ROOT}/lib" \
-S"$STAGE" \
-e "$STAGE${INSTALL_ROOT}/xtide-nearest" | sed -n 's/^shlibs:Depends=//p')"
[ -n "$DEPENDS" ] || DEPENDS="libc6"
# Final binary package control
cat > "$STAGE/DEBIAN/control" <<EOF
Package: $PKG
Version: ${DEB_VERSION}
Section: utils
Priority: optional
Architecture: ${DEB_ARCH}
Maintainer: ${GITHUB_REPOSITORY} <noreply@github.com>
Depends: $DEPENDS
Installed-Size: $SIZE_KB
Homepage: https://github.com/${GITHUB_REPOSITORY}
Description: xtide-nearest command-line tool
Find nearest tide/current station from an XTide harmonics database.
EOF
fakeroot dpkg-deb --build "$STAGE" "${PKG}_${DEB_VERSION}_${DEB_ARCH}.deb"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: linux-${{ matrix.arch }}
path: |
xtide-nearest-${{ env.ARTIFACT_TAG }}-${{ matrix.tar_suffix }}.tar.gz
xtide-nearest_${{ env.DEB_VERSION }}_${{ env.DEB_ARCH }}.deb
windows-x64:
name: windows (x64)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up MSYS2 (MINGW64)
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: >-
mingw-w64-x86_64-gcc
mingw-w64-x86_64-cmake
mingw-w64-x86_64-ninja
mingw-w64-x86_64-pkgconf
make
curl
tar
unzip
xz
- name: Compute artifact tag
shell: msys2 {0}
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "ARTIFACT_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
else
echo "ARTIFACT_TAG=sha-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
fi
- name: Build + install libtcd (prefix)
shell: msys2 {0}
run: |
set -euo pipefail
rm -rf _deps && mkdir -p _deps
cd _deps
curl -L --retry 3 --retry-delay 2 -o libtcd.tar.xz "${LIBTCD_URL}"
tar -xf libtcd.tar.xz
LIBTCD_DIR="$(find . -maxdepth 1 -type d -name 'libtcd-*' | head -n1)"
PREFIX="$PWD/libtcd-prefix"
cd "$LIBTCD_DIR"
./configure --prefix="$PREFIX"
make -j
make install
echo "TCD_PREFIX=$PREFIX" >> "$GITHUB_ENV"
- name: Configure (release)
shell: msys2 {0}
run: |
set -euo pipefail
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${{ env.TCD_PREFIX }}"
- name: Build
shell: msys2 {0}
run: |
cmake --build build -j
- name: Download harmonics (.tcd) for smoke test
shell: msys2 {0}
run: |
set -euo pipefail
rm -rf _deps/harmonics
mkdir -p _deps/harmonics
cd _deps/harmonics
curl -L --retry 3 --retry-delay 2 -o harmonics.tar.xz "${HARMONICS_URL}"
tar -xf harmonics.tar.xz || true
TCD_FILE="$(find . -type f -name '*.tcd' | head -n1)"
if [ -z "${TCD_FILE}" ]; then
echo "ERROR: No .tcd found after extracting harmonics archive"
find . -maxdepth 4 -type f | head -n 200 || true
exit 1
fi
echo "HARM_TCD=$PWD/${TCD_FILE#./}" >> "$GITHUB_ENV"
echo "Using HARM_TCD=$PWD/${TCD_FILE#./}"
- name: Download XTide Windows bundle (for tide.exe)
shell: msys2 {0}
run: |
set -euo pipefail
rm -rf _deps/xtide-win
mkdir -p _deps/xtide-win
curl -L --retry 3 --retry-delay 2 -o _deps/xtide-win.zip "${XTIDE_WIN64_ZIP_URL}"
unzip -q _deps/xtide-win.zip -d _deps/xtide-win
TIDE_EXE="$(find _deps/xtide-win -iname 'tide.exe' | head -n1)"
if [ ! -f "$TIDE_EXE" ]; then
echo "ERROR: tide.exe not found inside XTide bundle"
find _deps/xtide-win -maxdepth 4 -type f | head -n 100 || true
exit 1
fi
TIDE_EXE_WIN="$(cygpath -w "$TIDE_EXE")"
printf 'TIDE_EXE=%s\n' "$TIDE_EXE" >> "$GITHUB_ENV"
printf 'TIDE_EXE_WIN=%s\n' "$TIDE_EXE_WIN" >> "$GITHUB_ENV"
echo "Using TIDE_EXE=$TIDE_EXE"
echo "Using TIDE_EXE_WIN=$TIDE_EXE_WIN"
- name: Smoke test (Windows)
shell: msys2 {0}
run: |
set -euo pipefail
LAT="40.7050"
LON="-74.0120"
echo "=== Nearest TIDE station (lat=${LAT}, lon=${LON}) ==="
./build/xtide-nearest.exe --lat "${LAT}" --lon "${LON}" --tcd "${{ env.HARM_TCD }}" \
nearest --no-current --top 1
echo ""
echo "=== Nearest CURRENT station (lat=${LAT}, lon=${LON}) ==="
./build/xtide-nearest.exe --lat "${LAT}" --lon "${LON}" --tcd "${{ env.HARM_TCD }}" \
nearest --no-tide --top 1
echo ""
echo "=== tide.exe help (sanity) ==="
"${{ env.TIDE_EXE }}" -help 2>/dev/null | head -n 20 || true
export HFILE_PATH="$(dirname "${{ env.HARM_TCD }}")"
echo "HFILE_PATH=$HFILE_PATH"
STATION='Dimond Reef (depth 11 ft), Upper Bay, New York Harbor, New York Current'
echo ""
echo "=== Predict CURRENT via tide.exe (CSV, head) ==="
"${{ env.TIDE_EXE }}" -l "$STATION" \
-b "2026-01-22 00:00" \
-e "2026-01-22 06:00" \
-m r -f c -s "00:30" -z n -em pSsMm \
> out.csv
echo "--- out.csv (head) ---"
sed -n '1,12p' out.csv
test "$(wc -l < out.csv)" -ge 5
- name: Package zip
shell: msys2 {0}
run: |
set -euo pipefail
rm -rf dist xtide-nearest-${{ env.ARTIFACT_TAG }}-windows-x64.zip
mkdir -p dist
cp -f build/xtide-nearest.exe dist/xtide-nearest.exe
cp -f "${{ env.TIDE_EXE }}" dist/tide.exe
cp -f /mingw64/bin/libstdc++-6.dll dist/
cp -f /mingw64/bin/libgcc_s_seh-1.dll dist/
cp -f /mingw64/bin/libwinpthread-1.dll dist/ || true
if [ -d "${{ env.TCD_PREFIX }}/bin" ]; then
cp -f "${{ env.TCD_PREFIX }}/bin/"*.dll dist/ || true
fi
[ -f README.md ] && cp -f README.md dist/ || true
[ -f LICENSE ] && cp -f LICENSE dist/ || true
if ldd dist/xtide-nearest.exe | grep -qi "not found"; then
echo "ERROR: xtide-nearest.exe still has missing DLL deps:"
ldd dist/xtide-nearest.exe || true
exit 1
fi
if ldd dist/tide.exe | grep -qi "not found"; then
echo "ERROR: tide.exe still has missing DLL deps:"
ldd dist/tide.exe || true
exit 1
fi
( cd dist && cmake -E tar cfv ../xtide-nearest-${{ env.ARTIFACT_TAG }}-windows-x64.zip --format=zip . )
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: windows-x64
path: xtide-nearest-${{ env.ARTIFACT_TAG }}-windows-x64.zip
publish:
name: publish to GitHub Release (+ flat APT repo)
if: startsWith(github.ref, 'refs/tags/v')
needs: [linux, windows-x64]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
- name: Build flat APT repository (Packages/Release)
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y dpkg-dev apt-utils
rm -rf apt-repo
mkdir -p apt-repo
cp -v release-assets/linux-amd64/*.deb apt-repo/
cp -v release-assets/linux-arm64/*.deb apt-repo/
pushd apt-repo >/dev/null
dpkg-scanpackages --multiversion . /dev/null > Packages
gzip -9c Packages > Packages.gz
apt-ftparchive release . > Release
popd >/dev/null
- name: Publish versioned release (tag v*)
uses: softprops/action-gh-release@v2
with:
token: ${{ github.token }}
files: |
release-assets/linux-amd64/*.tar.gz
release-assets/linux-arm64/*.tar.gz
release-assets/windows-x64/*.zip
apt-repo/*
- name: Publish/update stable APT release (tag "apt")
uses: softprops/action-gh-release@v2
with:
token: ${{ github.token }}
tag_name: apt
name: APT repository (stable)
target_commitish: ${{ github.sha }}
overwrite_files: true
files: |
apt-repo/*