Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .github/actions/install-cached-snap/action.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

SNAP_NAME="${SNAP_NAME:?SNAP_NAME not set}"
CHANNEL="${CHANNEL:?CHANNEL not set}"
CLASSIC="${CLASSIC:?CLASSIC not set}"
CACHE_DIR="${CACHE_DIR:?CACHE_DIR not set}"
COMPONENTS="${COMPONENTS:-}"

declare -a COMPONENT_NAMES=()
if [ -n "$COMPONENTS" ]; then
IFS=',' read -r -a RAW_COMPONENTS <<<"$COMPONENTS"
for component in "${RAW_COMPONENTS[@]}"; do
component="${component//[[:space:]]/}"
if [ -n "$component" ]; then
COMPONENT_NAMES+=("$component")
fi
done
fi

mkdir -p "$CACHE_DIR"

find_snap_file() {
local snap_file=""
for snap in "$CACHE_DIR"/"${SNAP_NAME}"_*.snap; do
if [ -f "$snap" ]; then
snap_file="$snap"
break
fi
done
printf '%s\n' "$snap_file"
}

find_component_file() {
local component_name="$1"
local component_file=""
for comp in "$CACHE_DIR"/"${SNAP_NAME}+${component_name}"_*.comp; do
if [ -f "$comp" ]; then
component_file="$comp"
break
fi
done
printf '%s\n' "$component_file"
}

SNAP_FILE="$(find_snap_file)"
declare -a COMPONENT_FILES=()
CACHE_MISS=0

if [ -z "$SNAP_FILE" ]; then
CACHE_MISS=1
fi

for component_name in "${COMPONENT_NAMES[@]}"; do
component_file="$(find_component_file "$component_name")"
if [ -z "$component_file" ]; then
CACHE_MISS=1
fi
COMPONENT_FILES+=("$component_file")
done

if [ "$CACHE_MISS" -eq 1 ]; then
rm -f \
"$CACHE_DIR"/"${SNAP_NAME}"_*.snap \
"$CACHE_DIR"/"${SNAP_NAME}"_*.assert \
"$CACHE_DIR"/"${SNAP_NAME}"+*.comp

SNAP_DOWNLOAD_TARGET="$SNAP_NAME"
for component_name in "${COMPONENT_NAMES[@]}"; do
SNAP_DOWNLOAD_TARGET+="+${component_name}"
done

if [ -n "$CHANNEL" ]; then
snap download --target-directory "$CACHE_DIR" --channel "$CHANNEL" "$SNAP_DOWNLOAD_TARGET"
else
snap download --target-directory "$CACHE_DIR" "$SNAP_DOWNLOAD_TARGET"
fi

SNAP_FILE="$(find_snap_file)"
COMPONENT_FILES=()
for component_name in "${COMPONENT_NAMES[@]}"; do
COMPONENT_FILES+=("$(find_component_file "$component_name")")
done
fi

if [ -z "$SNAP_FILE" ]; then
echo "::error::Unable to locate or download snap for $SNAP_NAME"
exit 1
fi

for idx in "${!COMPONENT_NAMES[@]}"; do
if [ -z "${COMPONENT_FILES[$idx]}" ]; then
echo "::error::Unable to locate or download component ${COMPONENT_NAMES[$idx]} for $SNAP_NAME"
exit 1
fi
done

# Acknowledge assertion if present
ASSERT_FILE="${SNAP_FILE%.snap}.assert"
if [ -f "$ASSERT_FILE" ]; then
sudo snap ack "$ASSERT_FILE"
fi

INSTALL_ARGS=("$SNAP_FILE")
for component_file in "${COMPONENT_FILES[@]}"; do
INSTALL_ARGS+=("$component_file")
done

if [ "$CLASSIC" = "true" ]; then
sudo snap install --classic "${INSTALL_ARGS[@]}"
else
sudo snap install "${INSTALL_ARGS[@]}"
fi
75 changes: 75 additions & 0 deletions .github/actions/install-cached-snap/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Install Cached Snap
description: Download and install a snap with caching support
inputs:
snap-name:
description: Name of the snap to install
required: true
channel:
description: Snap store channel (e.g., latest/stable, latest/edge)
default: latest/stable
classic:
description: Whether to install with --classic flag
default: "false"
components:
description: Comma-separated list of snap components to install
default: ""
cache-dir:
description: Cache directory for downloaded snaps
default: ${{ github.workspace }}/.cache/snaps
enable-cache:
description: Enable snap caching (default true)
default: "true"
runs:
using: composite
steps:
- name: Normalize architecture
id: arch
shell: bash
run: |
case "${{ runner.arch }}" in
X64)
echo "normalized=amd64" >> $GITHUB_OUTPUT
;;
ARM64)
echo "normalized=arm64" >> $GITHUB_OUTPUT
;;
*)
echo "normalized=${{ runner.arch }}" >> $GITHUB_OUTPUT
;;
esac

- name: Resolve cache directory
id: cache-dir
shell: bash
run: |
cache_key="snap-${{ inputs.snap-name }}-for-${{ steps.arch.outputs.normalized }}-from-${{ inputs.channel }}"
if [ -n "${{ inputs.components }}" ]; then
safe_components="$(printf '%s' "${{ inputs.components }}" | sed 's/[^[:alnum:]._-]/_/g')"
scope="${{ inputs.snap-name }}-$safe_components-${{ steps.arch.outputs.normalized }}-${{ inputs.channel }}"
cache_key="${cache_key}-with-${safe_components}"
else
scope="${{ inputs.snap-name }}-${{ steps.arch.outputs.normalized }}-${{ inputs.channel }}"
fi
safe_scope="$(printf '%s' "$scope" | sed 's/[^[:alnum:]._-]/_/g')"
echo "path=${{ inputs.cache-dir }}/${safe_scope}" >> "$GITHUB_OUTPUT"
echo "key=${cache_key}" >> "$GITHUB_OUTPUT"

- name: Cache ${{ inputs.snap-name }} snap
if: ${{ inputs.enable-cache == 'true' }}
uses: actions/cache@v5
with:
path: ${{ steps.cache-dir.outputs.path }}
key: ${{ steps.cache-dir.outputs.key }}

- name: Install ${{ inputs.snap-name }} snap
shell: bash
env:
SNAP_NAME: ${{ inputs.snap-name }}
CHANNEL: ${{ inputs.channel }}
CLASSIC: ${{ inputs.classic }}
COMPONENTS: ${{ inputs.components }}
CACHE_DIR: ${{ steps.cache-dir.outputs.path }}
run: |
${{ github.action_path }}/action.sh
34 changes: 34 additions & 0 deletions .github/workflows/snapcraft-build-test-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Snapcraft Publishing

on:
push:
branches: [ main, snap/build-pipeline ]
tags: [ 'v*' ]
Comment on lines +7 to +8
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting a 🧵 for

main question is exactly when to trigger the new logic

Can we follow our current release strategy? Currently on push to main we publish a dev build, and on every vX.Y.Z tag we publish a tagged release.

We don't currently have the concept of a release candidate or promotion. Eventually we might want to add this, but right now we're optimizing for a fast release cadence.

It might be easier to keep snaps aligned with the current pattern, and then evolve our release strategy as a single unit from there.

pull_request:
branches: [ main ]
Comment on lines +9 to +10
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to remove, we don't want this building on PRs.

workflow_dispatch:
inputs:
skip-spread-tests:
type: boolean
description: "Skip integration tests (spread) and publish directly. Requires manual approval gate."
default: false
required: false

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref }}
cancel-in-progress: true

jobs:
snap-build-test-publish:
uses: ./.github/workflows/tasteful-crafts.yml
# Branch builds publish to vars.SNAPCRAFT_CHANNEL when set, otherwise to
# latest/edge. Tag builds publish to latest/candidate. Create matching
# GitHub environments and add an environment secret named
# SNAPCRAFT_STORE_CREDENTIALS to each one.
with:
snapstore-channel: ${{ github.ref_type == 'tag' && 'latest/candidate' || vars.SNAPCRAFT_CHANNEL || 'latest/edge' }}
skip-spread-tests: ${{ inputs.skip-spread-tests || false }}
secrets:
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
Loading
Loading