feat(snap): Snap Store packaging for Open CAD Studio

Repackages the released Linux AppImage (--appimage-extract) instead of
rebuilding from source, triggered on completion of the "Release" workflow.
Publishing to the Snap Store only happens once SNAPCRAFT_STORE_CREDENTIALS
is set as a repo secret.

Different from the other OpenAEC snaps in one important way: OCS uses
winit + wgpu (Vulkan/GL), not GTK/WebKit, so this uses explicit
opengl/wayland/x11 plugs and staged Mesa/X11/Wayland runtime libraries
instead of the `gnome` extension.

Higher confidence in the packaging mechanics (build succeeds, produces a
valid .snap) than in the actual runtime GPU/windowing behavior under
strict confinement, which needs real hands-on testing on a Linux desktop
before publishing to stable -- see the PR description.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Nozzit 2026-07-30 14:24:41 +02:00
commit 00952d1996
2 changed files with 253 additions and 0 deletions

169
.github/workflows/snap.yml vendored Normal file
View file

@ -0,0 +1,169 @@
name: Build Snap Package
on:
# workflow_run werkt alleen als dit workflowbestand op de DEFAULT branch staat.
# De workflow wordt getriggerd zodra "Release" afrondt (die triggert zelf op
# `release: published` + workflow_dispatch, niet op elke main-push).
workflow_run:
workflows: ["Release"]
types: [completed]
workflow_dispatch:
inputs:
version:
description: 'Bestaande release-tag om tegen te bouwen (test)'
required: true
default: 'v0.9.0'
# Minimale default; de build-snap-job zet zelf `contents: write` omdat hij de
# snap als asset aan de release hangt.
permissions:
contents: read
# Eén globale groep, NIET annuleren: twee releases kort na elkaar publiceren
# allebei naar het `stable`-kanaal van de Snap Store, en een Snap-release is
# niet in te trekken.
concurrency:
group: snap
cancel-in-progress: false
jobs:
build-snap:
timeout-minutes: 75
runs-on: ubuntu-22.04
# `types: [completed]` vuurt OOK als de release-workflow faalde of werd
# geannuleerd; zonder deze gate zou een mislukte release alsnog een snap
# bouwen. Geen extra head_branch-guard nodig zoals bij open-calc-studio:
# "Release" triggert uitsluitend op `release: published` + workflow_dispatch,
# nooit op een gewone main-push, dus er is geen vervuilingsscenario.
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
permissions:
contents: write
env:
# Job-level zodat de gated publish-stap er in `if:` naar kan verwijzen.
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
steps:
- uses: actions/checkout@v5
with:
# Bij workflow_run checkt checkout standaard de DEFAULT BRANCH uit,
# niet de tag/commit die de release-workflow bouwde — fout, want
# snap/snapcraft.yaml moet van de gebouwde commit komen. Daarom
# expliciet de head_sha van die workflow; voor workflow_dispatch
# (geen workflow_run-event) valt die weg naar github.ref (de branch
# waarop handmatig werd gedispatcht).
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
- name: Get version
id: get-version
env:
# Niet-vertrouwde invoer via env, NOOIT rechtstreeks ${} in de shell.
# GITHUB_*-namen zelf NIET zetten: gereserveerd en al aanwezig in de runner.
EVENT_NAME: ${{ github.event_name }}
DISPATCH_VERSION: ${{ github.event.inputs.version }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
VERSION="$DISPATCH_VERSION"
elif [ "$EVENT_NAME" = "workflow_run" ]; then
# head_branch is voor een `release`-getriggerde workflow_run niet
# betrouwbaar gegarandeerd de tagnaam (kan de default branch zijn) —
# daarom altijd de laatste gepubliceerde release opzoeken i.p.v. op
# head_branch te vertrouwen.
if printf '%s' "$HEAD_BRANCH" | grep -qE '^v[0-9]'; then
VERSION="$HEAD_BRANCH"
else
VERSION="$(gh release list --exclude-drafts --limit 1 --json tagName --jq '.[0].tagName // empty')"
fi
else
VERSION=""
fi
if [ -z "$VERSION" ]; then
echo "::error::Kon geen versie afleiden (event=$EVENT_NAME, head_branch=$HEAD_BRANCH)"
exit 1
fi
# `version` en `version_number` gaan verderop een `sed`-VERVANGINGSPATROON
# in, en een dispatch-input is vrije tekst — een '/' of '&' herschrijft dat
# patroon, een nieuwe regel schrijft een tweede regel in $GITHUB_OUTPUT
# (output-injectie). Whitelist: de `case` dekt de hele waarde (dus ook
# nieuwe regels), de regex daarna de structuur.
case "$VERSION" in
*[!v0-9.]*)
echo "::error::Versie bevat tekens buiten 'v', cijfers en punten." >&2
exit 1
;;
esac
if ! printf '%s' "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Versie '$VERSION' heeft niet de vorm vX.Y.Z." >&2
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "version_number=${VERSION#v}" >> "$GITHUB_OUTPUT"
# We bouwen de Rust-app NIET opnieuw: de snap herverpakt de AppImage van de
# gepubliceerde GitHub-release (--appimage-extract i.p.v. dpkg-deb -x, want
# dit project levert geen .deb).
- name: Wait for release AppImage and download it
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.get-version.outputs.version }}
run: |
echo "Wachten op AppImage-asset van release $VERSION..."
found=""
for i in $(seq 1 10); do
if gh release view "$VERSION" --json assets --jq '.assets[].name' 2>/dev/null | grep -q '\.AppImage$'; then
found=1
echo "AppImage-asset gevonden"
break
fi
echo "nog geen AppImage-asset... (poging $i/10)"
sleep 15
done
if [ -z "$found" ]; then
echo "::error::AppImage-asset voor $VERSION verscheen niet op tijd"
exit 1
fi
gh release download "$VERSION" -p '*.AppImage' --clobber --dir .
appimage=$(ls *.AppImage | head -1)
cp "$appimage" open-cad-studio.AppImage
echo "Gebruik $appimage -> open-cad-studio.AppImage"
- name: Set version in snapcraft.yaml
env:
VERSION_NUMBER: ${{ steps.get-version.outputs.version_number }}
run: |
sed -i "s/^version: .*/version: '$VERSION_NUMBER'/" snap/snapcraft.yaml
grep '^version:' snap/snapcraft.yaml
- name: Build snap
uses: snapcore/action-build@v1
id: snapcraft
# Echte release (via workflow_run): snap als asset aan de release hangen.
- name: Upload snap to GitHub release
if: github.event_name == 'workflow_run'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.get-version.outputs.version }}
SNAP: ${{ steps.snapcraft.outputs.snap }}
run: |
gh release upload "$VERSION" "$SNAP" --clobber
# Handmatige test: snap als CI-artifact (de release NIET aanraken).
- name: Upload snap as workflow artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: open-cad-studio-snap
path: ${{ steps.snapcraft.outputs.snap }}
if-no-files-found: error
# Live publiceren naar de Snap Store: alleen op een echte release én pas
# zodra het store-credentials-secret bestaat (eigenaar-stap) — én pas
# zodra de naam `open-cad-studio` op snapcraft.io geregistreerd is.
- name: Publish to Snap Store
if: github.event_name == 'workflow_run' && env.SNAPCRAFT_STORE_CREDENTIALS != ''
uses: snapcore/action-publish@v1
with:
snap: ${{ steps.snapcraft.outputs.snap }}
release: stable

84
snap/snapcraft.yaml Normal file
View file

@ -0,0 +1,84 @@
name: open-cad-studio
base: core22
# Versie wordt in CI overschreven met het release-versienummer (zie .github/workflows/snap.yml).
version: '0.0.0'
title: Open CAD Studio
summary: 2D drafting and 3D modeling with native DWG/DXF support
description: |
Open CAD Studio (OCS) is a CAD application for 2D drafting and 3D
modeling, built in Rust. Reads and writes DWG and DXF files natively
(R13 through R2018).
File formats:
- DWG and DXF read/write (R13-R2018)
- STL export, STEP AP203 export, OBJ import
- PDF export (plot layouts)
- WBLOCK, and XREF (attach/reload/auto-resolve external references)
2D drafting:
- Lines, polylines, rectangles, polygons, circles, arcs, ellipses,
splines
- Hatch fills with pattern, scale, and angle editing
- Offset, trim, extend, fillet, break, stretch, lengthen
- Array, mirror, move, copy, rotate, scale, explode
- Double-click text editing, and area/perimeter/centroid measurement
3D modeling:
- Solid primitives (box, sphere, cylinder), extrude, revolve, loft,
sweep, and 3D array
- ACIS tessellation for rendering 3DSOLID/REGION/BODY entities
Open CAD Studio is open source, part of the OpenAEC Foundation family
of applications, and rendered with wgpu for GPU-accelerated 2D/3D
drawing.
grade: stable
confinement: strict
apps:
open-cad-studio:
command: usr/bin/OpenCADStudio
desktop: usr/share/applications/io.github.HakanSeven12.OpenCadStudio.desktop
plugs:
- home # tekeningen openen/opslaan/exporteren in de home-map
- removable-media # idem op externe schijven/USB
- network # licentie-/Patreon-check
- opengl # GPU-toegang voor wgpu (Vulkan/GL)
- wayland
- x11
- desktop
- desktop-legacy
parts:
open-cad-studio:
plugin: nil
# De CI-stap kopieert de gepubliceerde release-AppImage naar de project-root
# als `open-cad-studio.AppImage`; we pakken die uit i.p.v. opnieuw te bouwen
# (dezelfde herverpak-aanpak als de .deb-gebaseerde OpenAEC-snaps, maar dan
# voor een AppImage: `--appimage-extract` levert een kant-en-klare AppDir).
override-build: |
set -eu
chmod +x "${CRAFT_PROJECT_DIR}/open-cad-studio.AppImage"
"${CRAFT_PROJECT_DIR}/open-cad-studio.AppImage" --appimage-extract
mkdir -p "${CRAFT_PART_INSTALL}/usr"
cp -r squashfs-root/usr/. "${CRAFT_PART_INSTALL}/usr/"
# OCS gebruikt winit (X11/Wayland) + wgpu (Vulkan met GL-fallback), geen
# GTK/WebKit — dus GEEN `extensions: [gnome]` (dat is voor de Tauri-apps).
# libvulkan1/mesa-vulkan-drivers zijn een fallback; echte hardware-versnelling
# loopt via de `opengl`-plug naar de host-driver.
stage-packages:
- libvulkan1
- mesa-vulkan-drivers
- libgl1
- libx11-6
- libxcursor1
- libxi6
- libxrandr2
- libxinerama1
- libxkbcommon0
- libxkbcommon-x11-0
- libwayland-client0
- libwayland-cursor0
- libwayland-egl1
- libfontconfig1
- libfreetype6