From 2d9b828b87f199bc9fb79c977d794fc38ead4692 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 16 Aug 2026 08:16:09 +0300 Subject: [PATCH] ci(snap): build from shared Linux payload --- .github/workflows/release.yml | 80 ++++++++++++++++ .github/workflows/snap.yml | 169 ---------------------------------- snap/snapcraft.yaml | 56 ++++++----- 3 files changed, 113 insertions(+), 192 deletions(-) delete mode 100644 .github/workflows/snap.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce1b99c9..6929d955 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,6 +43,17 @@ jobs: rsvg-convert -w 256 -h 256 assets/logo.svg \ -o AppDir/usr/share/icons/hicolor/256x256/apps/io.github.HakanSeven12.OpenCadStudio.png + - name: Pack shared Linux payload + run: tar -C AppDir -czf linux-payload.tar.gz usr + + - name: Upload shared Linux payload + uses: actions/upload-artifact@v4 + with: + name: linux-payload + path: linux-payload.tar.gz + if-no-files-found: error + retention-days: 7 + - name: Download linuxdeploy run: | wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage @@ -68,6 +79,75 @@ jobs: mv OpenCADStudio.AppImage "$ASSET" gh release upload ${{ github.ref_name }} "$ASSET" --clobber --repo ${{ github.repository }} + build-snap: + needs: build-appimage + runs-on: ubuntu-22.04 + timeout-minutes: 75 + concurrency: + group: snap + cancel-in-progress: false + permissions: + actions: read + contents: write + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download shared Linux payload + uses: actions/download-artifact@v4 + with: + name: linux-payload + path: . + + - name: Set package version + env: + EVENT_NAME: ${{ github.event_name }} + REF_NAME: ${{ github.ref_name }} + run: | + if [ "$EVENT_NAME" = "release" ]; then + VERSION="${REF_NAME#v}" + if [ "v$VERSION" != "$REF_NAME" ]; then + echo "::error::Release tag must be vX.Y.Z" + exit 1 + fi + else + VERSION="$(sed -n 's/^version = "\([0-9][0-9.]*\)"/\1/p' Cargo.toml | head -1)" + fi + if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Invalid package version: $VERSION" + exit 1 + fi + sed -i "s/^version: .*/version: '$VERSION'/" snap/snapcraft.yaml + + - name: Build snap + id: snapcraft + uses: snapcore/action-build@v1 + + - name: Upload snap to release + if: github.event_name == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SNAP: ${{ steps.snapcraft.outputs.snap }} + run: gh release upload "${{ github.ref_name }}" "$SNAP" --clobber + + - 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 + + - name: Publish snap to edge + if: github.event_name == 'release' && env.SNAPCRAFT_STORE_CREDENTIALS != '' + uses: snapcore/action-publish@v1 + with: + snap: ${{ steps.snapcraft.outputs.snap }} + release: edge + build-windows: runs-on: windows-latest permissions: diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml deleted file mode 100644 index 76fdcebc..00000000 --- a/.github/workflows/snap.yml +++ /dev/null @@ -1,169 +0,0 @@ -name: Build Snap Package - -on: - # workflow_run only works if this workflow file lives on the DEFAULT branch. - # Triggered as soon as "Release" finishes (which itself triggers on - # `release: published` + workflow_dispatch, not on every push to main). - workflow_run: - workflows: ["Release"] - types: [completed] - workflow_dispatch: - inputs: - version: - description: 'Existing release tag to build against (test)' - required: true - default: 'v0.9.0' - -# Minimal default; the build-snap job raises this to `contents: write` itself, -# because it attaches the snap as a release asset. -permissions: - contents: read - -# One global group, and deliberately NOT cancel-in-progress: two releases in -# quick succession would both publish to the Snap Store's `stable` channel, and -# a Snap release cannot be retracted. -concurrency: - group: snap - cancel-in-progress: false - -jobs: - build-snap: - timeout-minutes: 75 - runs-on: ubuntu-22.04 - # `types: [completed]` also fires when the release workflow FAILED or was - # cancelled; without this gate a failed release would still build a snap. - # No extra head_branch guard is needed here: "Release" only triggers on - # `release: published` + workflow_dispatch, never on an ordinary push to - # main, so there is no spurious-trigger scenario. - if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' - permissions: - contents: write - env: - # Job-level so the gated publish step can reference it from `if:`. - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} - - steps: - - uses: actions/checkout@v5 - with: - # On workflow_run, checkout defaults to the DEFAULT BRANCH rather than - # the tag/commit the release workflow built — wrong, because - # snap/snapcraft.yaml must come from the built commit. Hence the - # explicit head_sha of that workflow; for workflow_dispatch (no - # workflow_run event) it falls back to github.ref, i.e. the branch the - # manual dispatch was started from. - ref: ${{ github.event.workflow_run.head_sha || github.ref }} - - - name: Get version - id: get-version - env: - # Untrusted input goes through env, NEVER directly as ${} in the shell. - # Do not set GITHUB_* names here: they are reserved and already present. - 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 - # For a `release`-triggered workflow_run, head_branch is not - # guaranteed to be the tag name (it can be the default branch), so - # fall back to looking up the latest published release instead of - # trusting head_branch. - 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::Could not determine version (event=$EVENT_NAME, head_branch=$HEAD_BRANCH)" - exit 1 - fi - # `version` and `version_number` are fed into a `sed` REPLACEMENT - # PATTERN below, and a dispatch input is free text — a '/' or '&' would - # rewrite that pattern, and a newline would write a second line into - # $GITHUB_OUTPUT (output injection). Whitelist: the `case` covers the - # entire value (including newlines), the regex then checks structure. - case "$VERSION" in - *[!v0-9.]*) - echo "::error::Version contains characters other than 'v', digits and dots." >&2 - exit 1 - ;; - esac - if ! printf '%s' "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "::error::Version '$VERSION' is not of the form vX.Y.Z." >&2 - exit 1 - fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "version_number=${VERSION#v}" >> "$GITHUB_OUTPUT" - - # We do NOT rebuild the Rust app: the snap repackages the AppImage from the - # published GitHub release (--appimage-extract rather than dpkg-deb -x, - # since this project does not produce a .deb). - - name: Wait for release AppImage and download it - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ steps.get-version.outputs.version }} - run: | - echo "Waiting for AppImage asset of 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 found" - break - fi - echo "no AppImage asset yet... (attempt $i/10)" - sleep 15 - done - if [ -z "$found" ]; then - echo "::error::AppImage asset for $VERSION did not appear in time" - exit 1 - fi - gh release download "$VERSION" -p '*.AppImage' --clobber --dir . - appimage=$(ls *.AppImage | head -1) - cp "$appimage" open-cad-studio.AppImage - echo "Using $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 - - # Real release (via workflow_run): attach the snap as a release asset. - - 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 - - # Manual test run: snap as a CI artifact only, do NOT touch the release. - - 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 - - # Publishing live to the Snap Store: only on a real release, and only once - # the store credentials secret exists (an owner step) — and only once the - # name `open-cad-studio` has been registered on snapcraft.io. - - 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 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 01b7685c..fa38c66d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: open-cad-studio base: core22 -# Overwritten in CI with the release version number (see .github/workflows/snap.yml). +# Overwritten in CI with the release version number (see .github/workflows/release.yml). version: '0.0.0' title: Open CAD Studio summary: 2D drafting and 3D modeling with native DWG/DXF support @@ -35,15 +35,34 @@ description: | grade: stable confinement: strict +plugs: + graphics-core22: + interface: content + target: $SNAP/graphics + default-provider: mesa-core22 + +layout: + /usr/share/libdrm: + bind: $SNAP/graphics/libdrm + /usr/share/drirc.d: + symlink: $SNAP/graphics/drirc.d + /usr/share/X11/XErrorDB: + symlink: $SNAP/graphics/X11/XErrorDB + /usr/share/X11/locale: + symlink: $SNAP/graphics/X11/locale + apps: open-cad-studio: + command-chain: + - bin/graphics-core22-wrapper command: usr/bin/OpenCADStudio desktop: usr/share/applications/io.github.HakanSeven12.OpenCadStudio.desktop plugs: - home # open/save/export drawings in the home directory - removable-media # same, on external drives / USB - network # license / Patreon check - - opengl # GPU access for wgpu (Vulkan/GL) + - opengl + - graphics-core22 - wayland - x11 - desktop @@ -51,33 +70,24 @@ apps: parts: open-cad-studio: - plugin: nil - # The CI step copies the published release AppImage to the project root as - # `open-cad-studio.AppImage`; we unpack that instead of rebuilding from - # source. `--appimage-extract` yields a ready-made 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 uses winit (X11/Wayland) + wgpu (Vulkan with a GL fallback), not - # GTK/WebKit — so deliberately NO `extensions: [gnome]`. - # libvulkan1/mesa-vulkan-drivers are a fallback; real hardware acceleration - # goes through the `opengl` plug to the host driver. + plugin: dump + source: linux-payload.tar.gz 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 + + graphics-core22: + after: [open-cad-studio] + source: https://github.com/canonical/gpu-snap.git + plugin: dump + override-prime: | + craftctl default + ${CRAFT_PART_SRC}/bin/graphics-core22-cleanup mesa-core22 nvidia-core22 + prime: + - bin/graphics-core22-wrapper