release: Linux .deb and .rpm packages (#125)

cargo-deb and cargo-generate-rpm (pinned) package the same stripped binaries and files as the
tar.gz under /usr. The release job compares the file lists, runs lintian, and installs both
packages in Ubuntu 22.04 and Fedora containers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 17:25:34 -04:00
1 parent 48cae3eb1d
commit c8bd641e27
5 files changed
+169 -14

No files matched your search

+42 -5
View File
@@ -9,7 +9,7 @@
# and pushes to develop or tags never start it.
#
# macOS universal (arm64 + x86_64) .dmg with FieldCraft.app, the CLI as a .zip packaging/macos
# Linux x86_64 (glibc 2.35+) .tar.gz and .AppImage packaging/linux
# Linux x86_64 (glibc 2.35+) .tar.gz, .AppImage, .deb and .rpm packaging/linux
# Windows x64 .zip with fieldcraft.exe and fieldcraft-cli.exe packaging/windows
#
# macOS: signed with a Developer ID and notarized when the APPLE_* secrets exist, ad-hoc
@@ -119,33 +119,69 @@ jobs:
timeout-minutes: 90
env:
FIELDCRAFT_VERSION: ${{ needs.version.outputs.version }}
# The .deb and .rpm builders (both MIT), pinned.
CARGO_DEB_VERSION: 3.8.0
CARGO_GENERATE_RPM_VERSION: 0.21.0
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install packaging tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends desktop-file-utils
sudo apt-get install -y --no-install-recommends desktop-file-utils lintian rpm
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
key: release-linux-x86_64
# Release runs are rare: only release-branch runs keep a cache, so dry runs don't evict CI's.
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Build and package (tar.gz, AppImage)
- name: Install cargo-deb and cargo-generate-rpm
run: |
cargo install --locked --version "$CARGO_DEB_VERSION" cargo-deb
cargo install --locked --version "$CARGO_GENERATE_RPM_VERSION" cargo-generate-rpm
- name: Build and package (tar.gz, AppImage, deb, rpm)
run: packaging/linux/package.sh
- name: Check the packages
run: |
. packaging/env.sh
list="$(tar -tzf dist/release/*.tar.gz)"
base="fieldcraft-$VERSION-linux-x86_64"
deb="dist/release/fieldcraft_${VERSION}_amd64.deb"
rpm="dist/release/fieldcraft-$VERSION.x86_64.rpm"
list="$(tar -tzf "dist/release/$base.tar.gz")"
for f in bin/fieldcraft bin/fieldcraft-cli "share/applications/$APP_ID.desktop" \
share/doc/fieldcraft/THIRD-PARTY-LICENSES.txt; do
grep -Fx -- "fieldcraft-$VERSION-linux-x86_64/$f" <<<"$list"
grep -Fx -- "$base/$f" <<<"$list"
done
# The .deb and .rpm carry the tar.gz's files under /usr (the .deb adds its copyright
# file); their lists live in apps/fieldcraft/Cargo.toml, so a drift fails here.
files() { grep -v '/$' | sed -E "s#^(\./|/|$base/)##; s#^usr/##" | grep -vx 'share/doc/fieldcraft/copyright' | sort; }
diff <(files <<<"$list") <(dpkg-deb --fsys-tarfile "$deb" | tar -t | files)
diff <(files <<<"$list") <(rpm -qpl "$rpm" | files)
dpkg-deb --info "$deb"
rpm -qpi "$rpm"
echo "rpm requires:"; rpm -qpR "$rpm"
lintian --info --fail-on error "$deb"
ldd target/release/fieldcraft
for appimage in dist/release/*.AppImage; do
chmod +x "$appimage"
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version
done
- name: Install the .deb and .rpm in containers
run: |
# The oldest supported Ubuntu (the build's glibc) and a current Fedora. Recommended and
# weak dependencies (GPU drivers) are skipped to keep it quick; the hard ones must resolve.
docker run --rm -v "$PWD/dist/release:/dist:ro" ubuntu:22.04 bash -euxc '
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends /dist/*.deb
fieldcraft-cli --version
fieldcraft --version
test -f /usr/share/doc/fieldcraft/THIRD-PARTY-LICENSES.txt'
docker run --rm -v "$PWD/dist/release:/dist:ro" fedora:44 bash -euxc '
dnf install -y -q --setopt=install_weak_deps=False /dist/*.rpm
fieldcraft-cli --version
fieldcraft --version
test -f /usr/share/doc/fieldcraft/THIRD-PARTY-LICENSES.txt
dnf install -y -q rpmlint
rpmlint /dist/*.rpm || true'
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-linux-x86_64
@@ -201,6 +237,7 @@ jobs:
run: |
for f in "fieldcraft-$VERSION-macos-universal.dmg" "fieldcraft-cli-$VERSION-macos-universal.zip" \
"fieldcraft-$VERSION-linux-x86_64.tar.gz" "fieldcraft-$VERSION-linux-x86_64.AppImage" \
"fieldcraft_${VERSION}_amd64.deb" "fieldcraft-$VERSION.x86_64.rpm" \
"fieldcraft-$VERSION-windows-x64.zip"; do
test -s "artifacts/$f" || { echo "::error::missing $f"; exit 1; }
done
+1
View File
@@ -180,6 +180,7 @@ is committed and listed here with its exports.
| crates/mcp/src/ (JSON-RPC framing, MCP lifecycle, tool schema checks, the control-channel backend, base64) | SolidCraft `crates/mcp/src/` | MIT OR Apache-2.0 |
| crates/io/src/roots.rs (the rules of read and write roots) | VectorCraft's automation roots (docs/mcp.md there) | MIT OR Apache-2.0 |
| packaging/env.sh, packaging/.shellcheckrc, packaging/macos/package.sh, packaging/macos/Info.plist.in, packaging/linux/package.sh, packaging/linux/AppRun, packaging/windows/package.ps1 | SolidCraft `packaging/` and VectorCraft `packaging/` (signing and notarization in macos/package.sh) | MIT OR Apache-2.0 |
| apps/fieldcraft/Cargo.toml (the run-time dependency lists of `[package.metadata.deb]` and `[package.metadata.generate-rpm]`) | SolidCraft `packaging/linux/nfpm.yaml` | MIT OR Apache-2.0 |
| packaging/macos/import-cert.sh | VectorCraft `packaging/macos/import-cert.sh` | MIT OR Apache-2.0 |
| packaging/windows/check-exe.ps1 | SolidCraft `packaging/windows/check-icon.ps1` | MIT OR Apache-2.0 |
| apps/fieldcraft-web/ (the web runner, browser file services and downloads, Trunk configuration, index.html) | VectorCraft `apps/vectorcraft-web/` and SolidCraft `apps/solidcraft-web/` | MIT OR Apache-2.0 |
+81 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "fieldcraft"
description = "FieldCraft desktop app"
description = "Finite element modeling: meshing, model setup, solver decks and a linear solver"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -22,3 +22,83 @@ log = { workspace = true }
[build-dependencies]
# The Windows executable's icon resource (build.rs).
embed-resource = "3.0.12"
# Linux .deb and .rpm packages (cargo-deb, cargo-generate-rpm), built by packaging/linux/package.sh
# from the release binaries it has already built and stripped; it sets the version and the file
# names. The file list matches the tar.gz's (package.sh's staged tree; the release workflow
# compares them).
# The binaries link only glibc and libgcc_s. Windowing (Wayland, X11, xkbcommon), the GPU (Vulkan
# or EGL) and the file dialogs (the XDG desktop portal) are opened at run time, so they are no ELF
# NEEDED entries and the tools cannot find them: they are listed here.
[package.metadata.deb]
name = "fieldcraft"
maintainer = "FieldCraft contributors"
copyright = "FieldCraft contributors"
section = "science"
priority = "optional"
extended-description = """\
FieldCraft turns geometry into analysis-ready finite element models: volume \
meshes, materials, sections, boundary conditions, loads and steps. It writes \
Abaqus input, Nastran bulk data and LS-DYNA keyword decks, and solves linear \
static and modal problems with its own solver. Includes fieldcraft-cli, a \
headless command runner and MCP server."""
depends = "$auto, libxkbcommon0, libxkbcommon-x11-0, libwayland-client0, libx11-6, libx11-xcb1, libxcb1, libxcursor1, libxi6, libvulkan1 | libegl1"
recommends = "mesa-vulkan-drivers, libegl-mesa0, xdg-desktop-portal"
assets = [
["target/release/fieldcraft", "usr/bin/", "755"],
["target/release/fieldcraft-cli", "usr/bin/", "755"],
["../../packaging/linux/io.github.jyviko.fieldcraft.desktop", "usr/share/applications/", "644"],
["../../assets/app-icon/icon.iconset/icon_16x16.png", "usr/share/icons/hicolor/16x16/apps/io.github.jyviko.fieldcraft.png", "644"],
["../../assets/app-icon/icon.iconset/icon_32x32.png", "usr/share/icons/hicolor/32x32/apps/io.github.jyviko.fieldcraft.png", "644"],
["../../assets/app-icon/icon.iconset/icon_32x32@2x.png", "usr/share/icons/hicolor/64x64/apps/io.github.jyviko.fieldcraft.png", "644"],
["../../assets/app-icon/icon.iconset/icon_128x128.png", "usr/share/icons/hicolor/128x128/apps/io.github.jyviko.fieldcraft.png", "644"],
["../../assets/app-icon/icon.iconset/icon_256x256.png", "usr/share/icons/hicolor/256x256/apps/io.github.jyviko.fieldcraft.png", "644"],
["../../assets/app-icon/icon.iconset/icon_512x512.png", "usr/share/icons/hicolor/512x512/apps/io.github.jyviko.fieldcraft.png", "644"],
["../../assets/app-icon/icon.iconset/icon_512x512@2x.png", "usr/share/icons/hicolor/1024x1024/apps/io.github.jyviko.fieldcraft.png", "644"],
["../../README.md", "usr/share/doc/fieldcraft/", "644"],
["../../LICENSE-MIT", "usr/share/doc/fieldcraft/", "644"],
["../../LICENSE-APACHE", "usr/share/doc/fieldcraft/", "644"],
["../../NOTICE", "usr/share/doc/fieldcraft/", "644"],
["../../THIRD-PARTY-LICENSES.txt", "usr/share/doc/fieldcraft/", "644"],
]
[package.metadata.generate-rpm]
name = "fieldcraft"
license = "MIT OR Apache-2.0"
url = "https://github.com/jyviko/fieldcraft"
assets = [
{ source = "target/release/fieldcraft", dest = "/usr/bin/fieldcraft", mode = "755" },
{ source = "target/release/fieldcraft-cli", dest = "/usr/bin/fieldcraft-cli", mode = "755" },
{ source = "../../packaging/linux/io.github.jyviko.fieldcraft.desktop", dest = "/usr/share/applications/io.github.jyviko.fieldcraft.desktop", mode = "644" },
{ source = "../../assets/app-icon/icon.iconset/icon_16x16.png", dest = "/usr/share/icons/hicolor/16x16/apps/io.github.jyviko.fieldcraft.png", mode = "644" },
{ source = "../../assets/app-icon/icon.iconset/icon_32x32.png", dest = "/usr/share/icons/hicolor/32x32/apps/io.github.jyviko.fieldcraft.png", mode = "644" },
{ source = "../../assets/app-icon/icon.iconset/icon_32x32@2x.png", dest = "/usr/share/icons/hicolor/64x64/apps/io.github.jyviko.fieldcraft.png", mode = "644" },
{ source = "../../assets/app-icon/icon.iconset/icon_128x128.png", dest = "/usr/share/icons/hicolor/128x128/apps/io.github.jyviko.fieldcraft.png", mode = "644" },
{ source = "../../assets/app-icon/icon.iconset/icon_256x256.png", dest = "/usr/share/icons/hicolor/256x256/apps/io.github.jyviko.fieldcraft.png", mode = "644" },
{ source = "../../assets/app-icon/icon.iconset/icon_512x512.png", dest = "/usr/share/icons/hicolor/512x512/apps/io.github.jyviko.fieldcraft.png", mode = "644" },
{ source = "../../assets/app-icon/icon.iconset/icon_512x512@2x.png", dest = "/usr/share/icons/hicolor/1024x1024/apps/io.github.jyviko.fieldcraft.png", mode = "644" },
{ source = "../../README.md", dest = "/usr/share/doc/fieldcraft/README.md", mode = "644", doc = true },
{ source = "../../LICENSE-MIT", dest = "/usr/share/doc/fieldcraft/LICENSE-MIT", mode = "644", doc = true },
{ source = "../../LICENSE-APACHE", dest = "/usr/share/doc/fieldcraft/LICENSE-APACHE", mode = "644", doc = true },
{ source = "../../NOTICE", dest = "/usr/share/doc/fieldcraft/NOTICE", mode = "644", doc = true },
{ source = "../../THIRD-PARTY-LICENSES.txt", dest = "/usr/share/doc/fieldcraft/THIRD-PARTY-LICENSES.txt", mode = "644", doc = true },
]
# The binaries' ELF dependencies (glibc, libgcc_s) are found automatically; these are opened at run
# time. Soname provides rather than package names, so the same requirements resolve on Fedora/RHEL
# and openSUSE.
require-sh = false
[package.metadata.generate-rpm.requires]
"libxkbcommon.so.0()(64bit)" = "*"
"libxkbcommon-x11.so.0()(64bit)" = "*"
"libwayland-client.so.0()(64bit)" = "*"
"libX11.so.6()(64bit)" = "*"
"libX11-xcb.so.1()(64bit)" = "*"
"libxcb.so.1()(64bit)" = "*"
"libXcursor.so.1()(64bit)" = "*"
"libXi.so.6()(64bit)" = "*"
"(libvulkan.so.1()(64bit) or libEGL.so.1()(64bit))" = ""
[package.metadata.generate-rpm.recommends]
mesa-vulkan-drivers = "*"
xdg-desktop-portal = "*"
+17 -3
View File
@@ -11,7 +11,7 @@ are in `packaging/` and run the same way on a developer machine.
| Platform | Files | Script |
|---|---|---|
| macOS 11+, universal (arm64 + x86_64) | `fieldcraft-<v>-macos-universal.dmg` (FieldCraft.app on a drag-to-Applications image), `fieldcraft-cli-<v>-macos-universal.zip` | `packaging/macos/package.sh` |
| Linux x86_64, glibc 2.35+ | `fieldcraft-<v>-linux-x86_64.tar.gz` (`bin/`, `share/`), `fieldcraft-<v>-linux-x86_64.AppImage` | `packaging/linux/package.sh` |
| Linux x86_64, glibc 2.35+ | `fieldcraft-<v>-linux-x86_64.tar.gz` (`bin/`, `share/`), `fieldcraft-<v>-linux-x86_64.AppImage`, `fieldcraft_<v>_amd64.deb` (Debian, Ubuntu 22.04+), `fieldcraft-<v>.x86_64.rpm` (Fedora, RHEL, openSUSE) | `packaging/linux/package.sh` |
| Windows x64 | `fieldcraft-<v>-windows-x64.zip` (`fieldcraft.exe`, `fieldcraft-cli.exe`) | `packaging/windows/package.ps1` |
Plus `SHA256SUMS.txt` and the notes from `.github/release-notes.md` followed by GitHub's
@@ -23,6 +23,17 @@ entry and icon name, and the window's Wayland app_id / X11 class. The icons come
`icon.ico` embedded in `fieldcraft.exe` (`apps/fieldcraft/build.rs`), the iconset PNGs as the
Linux hicolor icons.
The `.deb` and `.rpm` are built by `cargo-deb` and `cargo-generate-rpm` (versions pinned in
`release.yml`) from the same stripped release binaries as the tar.gz, with the same files under
`/usr` (`/usr/bin`, the desktop entry, the hicolor icons, the licences in
`/usr/share/doc/fieldcraft`); their metadata and file lists are in `apps/fieldcraft/Cargo.toml`.
The binaries link only glibc; the windowing, GPU and file-dialog libraries they open at run time
are listed as dependencies by hand (Debian package names, RPM soname capabilities). A
pre-release `0.2.0-rc.1` is versioned `0.2.0~rc.1` inside the packages so it sorts before
`0.2.0`; the file names keep the release version. The Linux job compares both file lists with
the tar.gz, runs lintian, and installs the packages in Ubuntu 22.04 and Fedora containers to run
`fieldcraft-cli --version`.
## Signing
**macOS.** When the repository has the secrets below, the app, the dmg and the CLI are signed
@@ -85,7 +96,8 @@ Publishing is a maintainer's call; an agent preparing a release stops at the dra
run `shasum -a 256 -c SHA256SUMS.txt`, and check every platform's files are there.
3. Smoke-test what you can run: open the dmg, drag the app to Applications, launch it with
`--sample cantilever`; run `fieldcraft-cli --version` from the zip or tar.gz; on Linux run the
AppImage; on Windows unzip and start `fieldcraft.exe`. Look at the run's logs for the
AppImage, or install the `.deb` (`sudo apt install ./fieldcraft_<v>_amd64.deb`) or `.rpm`
(`sudo dnf install ./fieldcraft-<v>.x86_64.rpm`); on Windows unzip and start `fieldcraft.exe`. Look at the run's logs for the
platforms you could not try (each job checks its own package).
4. Edit the notes: what is new, fixes, breaking changes, known limitations. Keep the install and
signing paragraphs; never claim signing the notes don't state.
@@ -99,4 +111,6 @@ Publishing is a maintainer's call; an agent preparing a release stops at the dra
uploads them with the notes as the `release-assets` artifact of the run, without creating a
release. `-f draft=true` creates or updates the draft, and is accepted on `main` only.
Locally, `packaging/macos/package.sh --arch aarch64` (or `x86_64`, `universal`) and
`packaging/linux/package.sh` write to `dist/release/`.
`packaging/linux/package.sh` write to `dist/release/` (the `.deb` and `.rpm` need
`cargo install --locked cargo-deb cargo-generate-rpm` at the versions in `release.yml`;
`--formats "tar appimage"` skips them).
+28 -5
View File
@@ -4,12 +4,17 @@
# $DIST/fieldcraft-<version>-linux-x86_64.tar.gz bin/fieldcraft, bin/fieldcraft-cli, the
# .desktop entry, icons and licences
# $DIST/fieldcraft-<version>-linux-x86_64.AppImage the app as one file (chmod +x, run)
# $DIST/fieldcraft_<version>_amd64.deb Debian/Ubuntu package (cargo-deb)
# $DIST/fieldcraft-<version>.x86_64.rpm Fedora/RHEL/openSUSE package (cargo-generate-rpm)
#
# Usage: packaging/linux/package.sh [--skip-build] [--formats "tar appimage"]
# Usage: packaging/linux/package.sh [--skip-build] [--formats "tar appimage deb rpm"]
#
# Build on an old distribution (CI: Ubuntu 22.04, glibc 2.35) so the binaries run on newer ones.
# The AppImage uses appimagetool and the type 2 runtime at pinned releases, checked against their
# SHA-256 (downloaded into $CARGO_TARGET_DIR). Optional: desktop-file-validate.
# SHA-256 (downloaded into $CARGO_TARGET_DIR). The .deb and .rpm need cargo-deb and
# cargo-generate-rpm (the versions release.yml installs); their metadata and file lists are in
# apps/fieldcraft/Cargo.toml. FIELDCRAFT_MAINTAINER sets the .deb's Maintainer field.
# Optional: desktop-file-validate.
# Adapted from SolidCraft's packaging/linux/package.sh (MIT OR Apache-2.0; see ATTRIBUTION.md).
set -euo pipefail
# shellcheck source=../env.sh
@@ -18,12 +23,12 @@ HERE="$ROOT/packaging/linux"
ICONSET="$ROOT/assets/app-icon/icon.iconset"
SKIP_BUILD=0
FORMATS="tar appimage"
FORMATS="tar appimage deb rpm"
while [ $# -gt 0 ]; do
case "$1" in
--skip-build) SKIP_BUILD=1; shift ;;
--formats) FORMATS="$2"; shift 2 ;;
-h | --help) sed -n '2,13p' "$0"; exit 0 ;;
-h | --help) sed -n '2,18p' "$0"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
@@ -50,6 +55,8 @@ if [ "$SKIP_BUILD" = 0 ]; then
(cd "$ROOT" && cargo build --release --locked -p fieldcraft -p fieldcraft-cli)
fi
BIN="$CARGO_TARGET_DIR/release"
# Every format ships stripped binaries; cargo-generate-rpm packages target/release as it is.
strip "$BIN/fieldcraft" "$BIN/fieldcraft-cli"
WORK="$CARGO_TARGET_DIR/linux-package"
STAGE="$WORK/root/usr"
rm -rf "$WORK"
@@ -57,7 +64,6 @@ rm -rf "$WORK"
# ---- an FHS tree, shared by both formats --------------------------------------------------------
install -Dm755 "$BIN/fieldcraft" "$STAGE/bin/fieldcraft"
install -Dm755 "$BIN/fieldcraft-cli" "$STAGE/bin/fieldcraft-cli"
strip "$STAGE/bin/fieldcraft" "$STAGE/bin/fieldcraft-cli"
install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/share/applications/$APP_ID.desktop"
# hicolor icons: <size> <the iconset export of that size>.
while read -r size file; do
@@ -85,6 +91,23 @@ if has tar; then
echo "wrote $DIST/$BASENAME.tar.gz"
fi
# ---- .deb and .rpm ------------------------------------------------------------------------------
# From the binaries built and stripped above (--no-build). Semver pre-releases (0.2.0-rc.1) become
# 0.2.0~rc.1 inside the packages so they sort before the release; the file names keep $VERSION.
PKG_VERSION="$(printf '%s' "$VERSION" | sed 's/-/~/')"
if has deb; then
case "$ARCH" in x86_64) DEB_ARCH=amd64 ;; *) DEB_ARCH="$ARCH" ;; esac
OUT="$DIST/fieldcraft_${VERSION}_$DEB_ARCH.deb"
(cd "$ROOT" && cargo deb --locked -p fieldcraft --no-build --deb-version "$PKG_VERSION" \
--maintainer "${FIELDCRAFT_MAINTAINER:-FieldCraft contributors}" -o "$OUT")
echo "wrote $OUT"
fi
if has rpm; then
OUT="$DIST/fieldcraft-$VERSION.$ARCH.rpm"
(cd "$ROOT" && cargo generate-rpm -p apps/fieldcraft -s "version = \"$PKG_VERSION\"" -o "$OUT")
echo "wrote $OUT"
fi
# ---- AppImage -----------------------------------------------------------------------------------
# Usage: fetch <url> <sha256> <file>; downloads once and checks the hash every time.
fetch() {