Skip to content

CI

CI #220

Workflow file for this run

name: CI
on:
schedule:
- cron: '0 3 * * *' # nightly at 03:00 UTC
workflow_dispatch: # allows manual triggering
inputs:
create_release:
description: "Create new release"
required: true
type: boolean
pull_request: # validate the release build on PRs; the release job is skipped so nothing is published
paths:
# The build jobs clone stable-diffusion.cpp source from upstream
# (leejet/stable-diffusion.cpp), so only changes to this workflow itself
# can affect the outcome of a PR run. Scope PR runs to it.
- '.github/workflows/build.yml'
env:
BRANCH_NAME: master
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
ubuntu-latest-cmake:
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.15.1
- name: Dependencies
id: depends
run: |
sudo apt-get update
sudo apt-get install build-essential
- name: Build
id: cmake_build
run: |
mkdir build
cd build
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_INSTALL_RPATH='$ORIGIN'
cmake --build . --config Release
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Fetch system info
id: system-info
run: |
echo "CPU_ARCH=`uname -m`" >> "$GITHUB_OUTPUT"
echo "OS_NAME=`lsb_release -s -i`" >> "$GITHUB_OUTPUT"
echo "OS_VERSION=`lsb_release -s -r`" >> "$GITHUB_OUTPUT"
echo "OS_TYPE=`uname -s`" >> "$GITHUB_OUTPUT"
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
cp ggml/LICENSE ./build/bin/ggml.txt
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip ./build/bin/*
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip
path: |
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip
ubuntu-latest-cmake-vulkan:
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.15.1
- name: Dependencies
id: depends
run: |
sudo apt-get update
sudo apt-get install build-essential libvulkan-dev glslc spirv-headers
- name: Build
id: cmake_build
run: |
mkdir build
cd build
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DSD_VULKAN=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_INSTALL_RPATH='$ORIGIN'
cmake --build . --config Release
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Fetch system info
id: system-info
run: |
echo "CPU_ARCH=`uname -m`" >> "$GITHUB_OUTPUT"
echo "OS_NAME=`lsb_release -s -i`" >> "$GITHUB_OUTPUT"
echo "OS_VERSION=`lsb_release -s -r`" >> "$GITHUB_OUTPUT"
echo "OS_TYPE=`uname -s`" >> "$GITHUB_OUTPUT"
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
cp ggml/LICENSE ./build/bin/ggml.txt
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-vulkan.zip ./build/bin/*
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-vulkan.zip
path: |
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-vulkan.zip
ubuntu-latest-cuda:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
# On PRs only build one representative arch (packaging logic is identical
# across all sm_*); build the full matrix on schedule/dispatch.
sm: ${{ github.event_name == 'pull_request' && fromJSON('["sm_89"]') || fromJSON('["sm_75", "sm_80", "sm_86", "sm_89", "sm_90", "sm_100", "sm_120", "sm_121"]') }}
steps:
- name: Clone
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
ref: master
- name: ccache
uses: ggml-org/ccache-action@v1.2.16
with:
key: ubuntu-cuda-${{ matrix.sm }}
evict-old-files: 1d
- name: Free disk space
run: |
sudo apt-get remove -y '^aspnetcore-.*' '^dotnet-.*' '^llvm-.*' 'php.*' 'ruby.*' \
google-cloud-cli azure-cli google-chrome-stable firefox powershell 2>/dev/null || true
sudo apt-get autoremove -y
df -h
- name: Install CUDA Toolkit
run: |
wget -q https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
# The cuda-toolkit-12-9 metapackage is ~3.9 GB of archives and drags
# in Nsight, cuFFT, cuSPARSE, cuSOLVER and NPP, none of which
# ggml-cuda links or this workflow ships. Install just what the Build
# and Bundle steps consume (~1.2 GB): nvcc, cudart-dev (also supplies
# the libcuda stub behind CUDA::cuda_driver), cccl for thrust/cub,
# cublas, and the curand/nvjitlink libs the bundle step copies.
sudo apt-get install -y \
cuda-nvcc-12-9 \
cuda-cudart-dev-12-9 \
cuda-cccl-12-9 \
libcublas-dev-12-9 \
libcurand-dev-12-9 \
libnvjitlink-dev-12-9 \
cmake ninja-build patchelf
# Fail here, naming the missing file, rather than partway through the
# compile if NVIDIA repackages any of the above.
for f in /usr/local/cuda/bin/nvcc \
/usr/local/cuda/include/cublas_v2.h \
/usr/local/cuda/include/cub/cub.cuh \
/usr/local/cuda/lib64/libcudart.so \
/usr/local/cuda/lib64/libcublas.so \
/usr/local/cuda/lib64/libcublasLt.so \
/usr/local/cuda/lib64/libcurand.so \
/usr/local/cuda/lib64/libnvJitLink.so; do
[ -e "$f" ] || { echo "::error::CUDA install is missing $f"; exit 1; }
done
- name: Set CUDA environment
run: |
echo "CUDA_PATH=/usr/local/cuda" >> "$GITHUB_ENV"
echo "/usr/local/cuda/bin" >> "$GITHUB_PATH"
echo "LD_LIBRARY_PATH=/usr/local/cuda/lib64:${LD_LIBRARY_PATH:-}" >> "$GITHUB_ENV"
- name: Build
id: cmake_build
run: |
cmake_arch="${{ matrix.sm }}"
cmake_arch="${cmake_arch#sm_}"
cmake -B build -S . \
-DSD_CUBLAS=ON \
-DGGML_CUDA=ON \
-DCMAKE_CUDA_ARCHITECTURES="${cmake_arch}" \
-DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc \
-DGGML_NATIVE=OFF \
-DSD_BUILD_SHARED_LIBS=ON \
-DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j $(nproc)
- name: Bundle CUDA runtime libraries
run: |
cuda_lib=/usr/local/cuda/lib64
cp -av ${cuda_lib}/libcudart.so* build/bin/
cp -av ${cuda_lib}/libcublas.so* build/bin/
cp -av ${cuda_lib}/libcublasLt.so* build/bin/
cp -av ${cuda_lib}/libcurand.so* build/bin/
cp -av ${cuda_lib}/libnvJitLink.so* build/bin/
# Copy all ggml shared libs (libggml.so, libggml-base.so, libggml-cuda.so, etc.)
# from wherever cmake placed them outside build/bin/ into the distribution dir.
find build -name 'libggml*.so*' ! -path 'build/bin/*' -exec cp -av {} build/bin/ \;
- name: Set RPATH for portable distribution
run: |
for f in build/bin/*; do
[ -f "$f" ] && ! [ -L "$f" ] || continue
if file "$f" | grep -q 'ELF'; then
patchelf --set-rpath '$ORIGIN' "$f"
fi
done
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
cp ggml/LICENSE ./build/bin/ggml.txt
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
tar -I 'xz -T0' -cf sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-ubuntu-cuda-${{ matrix.sm }}-x64.tar.xz -C ./build/bin .
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-ubuntu-cuda-${{ matrix.sm }}-x64.tar.xz
path: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-ubuntu-cuda-${{ matrix.sm }}-x64.tar.xz
windows-latest-cuda:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
# On PRs only build one representative arch (packaging logic is identical
# across all sm_*); build the full matrix on schedule/dispatch.
sm: ${{ github.event_name == 'pull_request' && fromJSON('["sm_89"]') || fromJSON('["sm_75", "sm_80", "sm_86", "sm_89", "sm_90", "sm_100", "sm_120", "sm_121"]') }}
steps:
- name: Clone
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
ref: master
- name: Install CUDA Toolkit
id: cuda-toolkit
uses: Jimver/cuda-toolkit@v0.2.35
with:
cuda: '12.9.1'
method: 'network'
sub-packages: '["nvcc", "cudart", "cublas", "cublas_dev", "curand", "nvjitlink", "thrust", "visual_studio_integration"]'
- name: ccache
uses: ggml-org/ccache-action@v1.2.16
with:
key: windows-cuda-${{ matrix.sm }}
variant: ccache
evict-old-files: 1d
- name: Install Ninja
run: choco install ninja
- name: Build
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
set sm=${{ matrix.sm }}
set cmake_arch=%sm:sm_=%
cmake -S . -B build -G "Ninja Multi-Config" ^
-DSD_CUBLAS=ON ^
-DGGML_CUDA=ON ^
-DCMAKE_CUDA_ARCHITECTURES=%cmake_arch% ^
-DGGML_NATIVE=OFF ^
-DSD_BUILD_SHARED_LIBS=ON ^
-DCMAKE_CXX_FLAGS=/bigobj
cmake --build build --config Release
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
Copy-Item ggml/LICENSE .\build\bin\Release\ggml.txt
Copy-Item LICENSE .\build\bin\Release\stable-diffusion.cpp.txt
$cudaBin = Join-Path $env:CUDA_PATH 'bin'
$runtimeDllPatterns = @(
'cudart64_*.dll',
'cublas64_*.dll',
'cublasLt64_*.dll',
'curand64_*.dll',
'nvJitLink_*.dll'
)
foreach ($pattern in $runtimeDllPatterns) {
$dll = Get-ChildItem -Path $cudaBin -Filter $pattern -ErrorAction Stop | Sort-Object Name -Descending | Select-Object -First 1
if (-not $dll) { throw "Required CUDA runtime DLL matching '$pattern' was not found in $cudaBin" }
Copy-Item $dll.FullName .\build\bin\Release
}
# Copy all ggml DLLs (ggml.dll, ggml-base.dll, ggml-cuda.dll, etc.)
# from wherever cmake placed them outside build\bin\Release\ into the distribution dir.
Get-ChildItem -Path build -Filter 'ggml*.dll' -Recurse |
Where-Object { $_.FullName -notlike '*\bin\Release\*' } |
ForEach-Object { Copy-Item $_.FullName .\build\bin\Release\ -Force }
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-windows-cuda-${{ matrix.sm }}-x64.zip .\build\bin\Release\*
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-windows-cuda-${{ matrix.sm }}-x64.zip
path: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-windows-cuda-${{ matrix.sm }}-x64.zip
ubuntu-arm64-cuda:
# GB10 (sm_121) ARM64 Linux build
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
# sm_121 targets the GB10 (Blackwell) ARM64 device
sm: ["sm_121"]
steps:
- name: Clone
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
ref: master
- name: ccache
uses: ggml-org/ccache-action@v1.2.16
with:
key: ubuntu-arm64-cuda-${{ matrix.sm }}
evict-old-files: 1d
- name: Install CUDA Toolkit
run: |
wget -q https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/sbsa/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
# See the x64 job above for why this is a hand-picked subset rather
# than the cuda-toolkit-12-9 metapackage.
sudo apt-get install -y \
cuda-nvcc-12-9 \
cuda-cudart-dev-12-9 \
cuda-cccl-12-9 \
libcublas-dev-12-9 \
libcurand-dev-12-9 \
libnvjitlink-dev-12-9 \
cmake ninja-build patchelf
for f in /usr/local/cuda/bin/nvcc \
/usr/local/cuda/include/cublas_v2.h \
/usr/local/cuda/include/cub/cub.cuh \
/usr/local/cuda/lib64/libcudart.so \
/usr/local/cuda/lib64/libcublas.so \
/usr/local/cuda/lib64/libcublasLt.so \
/usr/local/cuda/lib64/libcurand.so \
/usr/local/cuda/lib64/libnvJitLink.so; do
[ -e "$f" ] || { echo "::error::CUDA install is missing $f"; exit 1; }
done
- name: Set CUDA environment
run: |
echo "CUDA_PATH=/usr/local/cuda" >> "$GITHUB_ENV"
echo "/usr/local/cuda/bin" >> "$GITHUB_PATH"
echo "LD_LIBRARY_PATH=/usr/local/cuda/lib64:${LD_LIBRARY_PATH:-}" >> "$GITHUB_ENV"
- name: Build
id: cmake_build
run: |
cmake_arch="${{ matrix.sm }}"
cmake_arch="${cmake_arch#sm_}"
cmake -B build -S . \
-DSD_CUBLAS=ON \
-DGGML_CUDA=ON \
-DCMAKE_CUDA_ARCHITECTURES="${cmake_arch}" \
-DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc \
-DGGML_NATIVE=OFF \
-DSD_BUILD_SHARED_LIBS=ON \
-DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j $(nproc)
- name: Bundle CUDA runtime libraries
run: |
cuda_lib=/usr/local/cuda/lib64
cp -av ${cuda_lib}/libcudart.so* build/bin/
cp -av ${cuda_lib}/libcublas.so* build/bin/
cp -av ${cuda_lib}/libcublasLt.so* build/bin/
cp -av ${cuda_lib}/libcurand.so* build/bin/
cp -av ${cuda_lib}/libnvJitLink.so* build/bin/
find build -name 'libggml*.so*' ! -path 'build/bin/*' -exec cp -av {} build/bin/ \;
- name: Set RPATH for portable distribution
run: |
for f in build/bin/*; do
[ -f "$f" ] && ! [ -L "$f" ] || continue
if file "$f" | grep -q 'ELF'; then
patchelf --set-rpath '$ORIGIN' "$f"
fi
done
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
cp ggml/LICENSE ./build/bin/ggml.txt
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
tar -I 'xz -T0' -cf sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-ubuntu-cuda-${{ matrix.sm }}-arm64.tar.xz -C ./build/bin .
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-ubuntu-cuda-${{ matrix.sm }}-arm64.tar.xz
path: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-ubuntu-cuda-${{ matrix.sm }}-arm64.tar.xz
windows-latest-cmake:
runs-on: windows-2022
env:
VULKAN_VERSION: 1.4.328.1
strategy:
matrix:
include:
- build: "cpu"
defines: "-DGGML_NATIVE=OFF -DSD_BUILD_SHARED_LIBS=ON -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
- build: "cuda12"
defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES='61;70;75;80;86;89;90;100;120' -DCMAKE_CUDA_FLAGS='-Xcudafe \"--diag_suppress=177\" -Xcudafe \"--diag_suppress=550\"' -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
- build: "vulkan"
defines: "-DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
steps:
- name: Clone
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.15.1
- name: Install cuda-toolkit
id: cuda-toolkit
if: ${{ matrix.build == 'cuda12' }}
uses: Jimver/cuda-toolkit@v0.2.22
with:
cuda: "12.8.1"
method: "network"
sub-packages: '["nvcc", "cudart", "cublas", "cublas_dev", "thrust", "visual_studio_integration"]'
- name: Install Vulkan SDK
id: get_vulkan
if: ${{ matrix.build == 'vulkan' }}
run: |
curl.exe -o $env:RUNNER_TEMP/VulkanSDK-Installer.exe -L "https://sdk.lunarg.com/sdk/download/${env:VULKAN_VERSION}/windows/vulkansdk-windows-X64-${env:VULKAN_VERSION}.exe"
& "$env:RUNNER_TEMP\VulkanSDK-Installer.exe" --accept-licenses --default-answer --confirm-command install
Add-Content $env:GITHUB_ENV "VULKAN_SDK=C:\VulkanSDK\${env:VULKAN_VERSION}"
Add-Content $env:GITHUB_PATH "C:\VulkanSDK\${env:VULKAN_VERSION}\bin"
- name: ccache
uses: ggml-org/ccache-action@v1.2.16
with:
key: windows-cmake-${{ matrix.build }}
variant: ccache
evict-old-files: 1d
max-size: ${{ matrix.build == 'cuda12' && '2G' || '500M' }}
- name: Activate MSVC environment
id: msvc_dev_cmd
uses: ilammy/msvc-dev-cmd@v1
- name: Build
id: cmake_build
run: |
mkdir build
cd build
cmake .. -DCMAKE_CXX_FLAGS='/bigobj' -G Ninja -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_BUILD_TYPE=Release ${{ matrix.defines }}
cmake --build .
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
$filePath = ".\build\bin\Release\*"
if (Test-Path $filePath) {
echo "Exists at path $filePath"
Copy-Item ggml/LICENSE .\build\bin\Release\ggml.txt
Copy-Item LICENSE .\build\bin\Release\stable-diffusion.cpp.txt
} elseif (Test-Path ".\build\bin\stable-diffusion.dll") {
$filePath = ".\build\bin\*"
echo "Exists at path $filePath"
Copy-Item ggml/LICENSE .\build\bin\ggml.txt
Copy-Item LICENSE .\build\bin\stable-diffusion.cpp.txt
} else {
ls .\build\bin
throw "Can't find stable-diffusion.dll"
}
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip $filePath
- name: Copy and pack Cuda runtime
id: pack_cuda_runtime
if: ${{ matrix.build == 'cuda12' && (github.event_name == 'schedule' || github.event.inputs.create_release == 'true') }}
run: |
echo "Cuda install location: ${{steps.cuda-toolkit.outputs.CUDA_PATH}}"
$dst='.\build\bin\cudart\'
robocopy "${{steps.cuda-toolkit.outputs.CUDA_PATH}}\bin" $dst cudart64_*.dll cublas64_*.dll cublasLt64_*.dll
7z a cudart-sd-bin-win-cu12-x64.zip $dst\*
- name: Upload Cuda runtime
if: ${{ matrix.build == 'cuda12' && (github.event_name == 'schedule' || github.event.inputs.create_release == 'true') }}
uses: actions/upload-artifact@v4
with:
name: sd-cudart-sd-bin-win-cu12-x64.zip
path: |
cudart-sd-bin-win-cu12-x64.zip
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip
path: |
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip
windows-latest-rocm:
runs-on: windows-2022
env:
ROCM_VERSION: "7.14.0"
GPU_TARGETS: "gfx906;gfx908;gfx90a;gfx942;gfx950;gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1150;gfx1151;gfx1152;gfx1200;gfx1201"
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.15.1
- name: Cache ROCm Installation
id: cache-rocm
uses: actions/cache@v4
with:
path: C:\TheRock\build
key: rocm-wheels-${{ env.ROCM_VERSION }}-${{ runner.os }}
- name: ccache
uses: ggml-org/ccache-action@v1.2.16
with:
key: windows-rocm-${{ env.ROCM_VERSION }}-x64
evict-old-files: 1d
- name: Install ROCm with Wheels
if: steps.cache-rocm.outputs.cache-hit != 'true'
run: |
$ErrorActionPreference = "Stop"
write-host "Setting up Python virtual environment"
# Create the venv directly at the cache location to avoid relocation issues
New-Item -Path "C:\TheRock\build" -ItemType Directory -Force | Out-Null
python -m venv C:\TheRock\build\.venv
& C:\TheRock\build\.venv\Scripts\Activate.ps1
write-host "Upgrading pip"
python -m pip install --upgrade pip
write-host "Installing ROCm wheels for multi-arch support"
# Install ROCm wheels for multi-arch support (this may take several minutes)
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]"
# Pre-expand the devel tree so it is included in the cache
write-host "Initializing ROCm devel tree"
rocm-sdk init
if ($LASTEXITCODE -ne 0) { throw "rocm-sdk init failed with exit code $LASTEXITCODE" }
write-host "Completed ROCm wheel installation to C:\TheRock\build"
- name: Setup ROCm Environment
run: |
$ErrorActionPreference = "Stop"
# Activate venv from cache or fresh install
& C:\TheRock\build\.venv\Scripts\Activate.ps1
# Expand the devel tree (idempotent; no-op if already done during install)
rocm-sdk init
if ($LASTEXITCODE -ne 0) { throw "rocm-sdk init failed with exit code $LASTEXITCODE" }
# Get ROCm installation paths using the rocm-sdk CLI tool
$rocmPath = (rocm-sdk path --root)
if (-not $rocmPath) { throw "rocm-sdk path --root returned empty - devel package may not be installed" }
$rocmPath = $rocmPath.Trim()
$cmakePath = (rocm-sdk path --cmake).Trim()
$binPath = (rocm-sdk path --bin).Trim()
write-host "ROCm root: $rocmPath"
write-host "CMake path: $cmakePath"
write-host "Bin path: $binPath"
echo "HIP_PATH=$rocmPath" >> $env:GITHUB_ENV
echo "CMAKE_PREFIX_PATH=$cmakePath" >> $env:GITHUB_ENV
echo "HIP_DEVICE_LIB_PATH=$rocmPath\lib\llvm\amdgcn\bitcode" >> $env:GITHUB_ENV
echo "HIP_PLATFORM=amd" >> $env:GITHUB_ENV
echo "LLVM_PATH=$rocmPath\lib\llvm" >> $env:GITHUB_ENV
echo "$binPath" >> $env:GITHUB_PATH
# Keep venv in PATH for subsequent steps
echo "C:\TheRock\build\.venv\Scripts" >> $env:GITHUB_PATH
- name: Build
run: |
mkdir build
cd build
cmake .. `
-G "Unix Makefiles" `
-DCMAKE_PREFIX_PATH="${env:HIP_PATH}" `
-DSD_HIPBLAS=ON `
-DSD_BUILD_SHARED_LIBS=ON `
-DGGML_NATIVE=OFF `
-DCMAKE_C_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
-DCMAKE_CXX_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang++.exe" `
-DCMAKE_HIP_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
-DHIP_PATH="${env:HIP_PATH}" `
-DCMAKE_BUILD_TYPE=Release `
-DGPU_TARGETS="${{ env.GPU_TARGETS }}"
cmake --build . --config Release --parallel ${env:NUMBER_OF_PROCESSORS}
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: pr-mpt/actions-commit-hash@v2
- name: Pack artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip .\build\bin\*
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip
path: |
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip
ubuntu-latest-rocm:
runs-on: ubuntu-24.04
env:
UBUNTU_VERSION: "24.04"
strategy:
matrix:
include:
- ROCM_VERSION: "7.14.0"
gpu_targets: "gfx900;gfx906;gfx908;gfx90a;gfx90c;gfx942;gfx950;gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1150;gfx1151;gfx1152;gfx1200;gfx1201"
build: x64
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
- name: ccache
uses: ggml-org/ccache-action@v1.2.16
with:
key: ubuntu-rocm-cmake-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
evict-old-files: 1d
- name: Dependencies
id: depends
run: |
sudo apt install -y build-essential git cmake wget
- name: Free disk space
run: |
df -h
# Remove preinstalled SDKs and caches not needed for this job before
# installing ROCm. The legacy ROCm apt packages are large enough to
# exhaust ubuntu-latest if cleanup runs after installation.
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /usr/local/lib/android || true
sudo rm -rf /opt/ghc || true
sudo rm -rf /usr/local/.ghcup || true
sudo rm -rf /opt/hostedtoolcache || true
sudo rm -rf /usr/share/swift || true
sudo rm -rf /usr/local/share/boost || true
docker system prune -af || true
sudo apt clean
df -h
- name: Setup TheRock with Wheels
id: therock_env
run: |
# Create Python virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install ROCm wheels for build
# libraries = HIP runtime and CMake configs needed for linking
# devel = compilers, headers, static libs
python -m pip install --upgrade pip
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]"
# Get ROCm installation paths using the rocm-sdk CLI tool
ROCM_PATH=$(rocm-sdk path --root)
CMAKE_PATH=$(rocm-sdk path --cmake)
BIN_PATH=$(rocm-sdk path --bin)
echo "ROCM_PATH=$ROCM_PATH"
echo "CMAKE_PATH=$CMAKE_PATH"
echo "BIN_PATH=$BIN_PATH"
# Set environment variables
echo "ROCM_PATH=$ROCM_PATH" >> $GITHUB_ENV
echo "CMAKE_PREFIX_PATH=$CMAKE_PATH" >> $GITHUB_ENV
echo "HIP_PATH=$ROCM_PATH" >> $GITHUB_ENV
echo "PATH=$BIN_PATH:${PATH}" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=$ROCM_PATH/lib:${LD_LIBRARY_PATH:-}" >> $GITHUB_ENV
# Keep venv activated for subsequent steps
echo "$(pwd)/.venv/bin" >> $GITHUB_PATH
# setup-node installs into /opt/hostedtoolcache, which is removed above.
# Keep Node/pnpm setup after disk cleanup so the server frontend can be embedded.
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.15.1
- name: Build
id: cmake_build
run: |
mkdir build
cd build
cmake .. -G Ninja \
-DCMAKE_HIP_COMPILER="$(hipconfig -l)/clang" \
-DCMAKE_HIP_FLAGS="-mllvm --amdgpu-unroll-threshold-local=600" \
-DCMAKE_BUILD_TYPE=Release \
-DSD_HIPBLAS=ON \
-DHIP_PLATFORM=amd \
-DGPU_TARGETS="${{ matrix.gpu_targets }}" \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
-DGGML_NATIVE=OFF \
-DSD_BUILD_SHARED_GGML_LIB=ON \
-DGGML_BACKEND_DL=ON \
-DGGML_CPU_ALL_VARIANTS=ON \
-DSD_BUILD_SHARED_LIBS=ON
cmake --build . --config Release
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Prepare artifacts
id: prepare_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
# Copy licenses
cp ggml/LICENSE ./build/bin/ggml.txt
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
- name: Fetch system info
id: system-info
run: |
echo "CPU_ARCH=`uname -m`" >> "$GITHUB_OUTPUT"
echo "OS_NAME=`lsb_release -s -i`" >> "$GITHUB_OUTPUT"
echo "OS_VERSION=`lsb_release -s -r`" >> "$GITHUB_OUTPUT"
echo "OS_TYPE=`uname -s`" >> "$GITHUB_OUTPUT"
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
cp ggml/LICENSE ./build/bin/ggml.txt
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
zip -y -r sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-Ubuntu-${{ env.UBUNTU_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-rocm-${{ matrix.ROCM_VERSION }}.zip ./build/bin
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-Ubuntu-${{ env.UBUNTU_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-rocm-${{ matrix.ROCM_VERSION }}.zip
path: |
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-Ubuntu-${{ env.UBUNTU_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-rocm-${{ matrix.ROCM_VERSION }}.zip
macos-arm64-cmake:
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
label: macos-14
- os: macos-latest
label: macos-latest
runs-on: ${{ matrix.os }}
steps:
- name: Clone
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
- name: ccache
uses: ggml-org/ccache-action@v1.2.21
with:
key: macos-arm64-${{ matrix.label }}
evict-old-files: 1d
- name: Build
id: cmake_build
run: |
sysctl -a
mkdir build
cd build
cmake .. \
-DGGML_METAL=ON \
-DGGML_METAL_USE_BF16=ON \
-DGGML_METAL_EMBED_LIBRARY=ON \
-DSD_BUILD_SHARED_LIBS=ON \
-DCMAKE_INSTALL_RPATH='@loader_path' \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release -j $(sysctl -n hw.logicalcpu)
- name: Get commit hash
id: commit
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: prompt/actions-commit-hash@v2
- name: Fetch system info
id: system-info
run: |
echo "CPU_ARCH=`uname -m`" >> "$GITHUB_OUTPUT"
echo "OS_NAME=`sw_vers -productName`" >> "$GITHUB_OUTPUT"
echo "OS_VERSION=`sw_vers -productVersion`" >> "$GITHUB_OUTPUT"
echo "OS_TYPE=`uname -s`" >> "$GITHUB_OUTPUT"
- name: Pack artifacts
id: pack_artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
run: |
cp ggml/LICENSE ./build/bin/ggml.txt
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip ./build/bin/*
- name: Upload artifacts
if: ${{ github.event_name == 'schedule' || github.event.inputs.create_release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip
path: |
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip
release:
# always() overrides the default behavior of skipping this job when a
# needed build job fails (e.g. one CUDA sm_* matrix leg fails while
# sibling legs succeed and upload). The job must still run so the
# per-family checks below can decide what's actually complete and safe
# to publish.
if: ${{ always() && (github.event_name == 'schedule' || github.event.inputs.create_release == 'true') }}
permissions:
contents: write
runs-on: ubuntu-latest
needs:
- ubuntu-latest-cmake
- ubuntu-latest-cmake-vulkan
- ubuntu-latest-cuda
- ubuntu-arm64-cuda
- windows-latest-cuda
- ubuntu-latest-rocm
- macos-arm64-cmake
- windows-latest-cmake
- windows-latest-rocm
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
repository: 'leejet/stable-diffusion.cpp'
- name: Download artifacts
id: download-artifact
uses: actions/download-artifact@v4
with:
path: ./artifact
pattern: sd-*
merge-multiple: true
- name: Get commit count
id: commit_count
run: |
echo "count=$(git rev-list --count HEAD)" >> $GITHUB_OUTPUT
- name: Get commit hash
id: commit
uses: prompt/actions-commit-hash@v2
- name: Generate tag name
id: tag_name
run: |
TAG_NAME="${{ env.BRANCH_NAME }}-${{ steps.commit_count.outputs.count }}-${{ steps.commit.outputs.short }}"
echo "name=${TAG_NAME}" >> $GITHUB_OUTPUT
- name: Check release artifacts
id: check_artifacts
env:
BRANCH: ${{ env.BRANCH_NAME }}
SHORT: ${{ steps.commit.outputs.short }}
run: |
total_missing=0
summary=""
check_family() {
local family="$1"; shift
local patterns=("$@")
local missing=0
for pattern in "${patterns[@]}"; do
if ! ls $pattern >/dev/null 2>&1; then
echo "::error::[$family] Missing artifact: $pattern"
missing=$((missing + 1))
fi
done
if [ "$missing" -gt 0 ]; then
echo "::warning::$family family is incomplete ($missing artifact(s) missing) — dropping all $family artifacts from this release."
for pattern in "${patterns[@]}"; do
rm -fv $pattern 2>/dev/null || true
done
total_missing=$((total_missing + missing))
summary="${summary}${summary:+, }${family}: $missing missing, family dropped"
fi
}
# Each backend family must be published as a complete, internally
# consistent set across every platform/variant/SDK it covers — never
# a partial mix. A gap in one family drops that whole family from
# the release; other families are unaffected.
# CUDA: 8 sm_* variants on ubuntu x64 and windows x64; only sm_121
# (GB10) is built for ubuntu arm64.
cuda_patterns=()
for sm in sm_75 sm_80 sm_86 sm_89 sm_90 sm_100 sm_120 sm_121; do
cuda_patterns+=("artifact/sd-${BRANCH}-${SHORT}-ubuntu-cuda-${sm}-x64.tar.xz")
cuda_patterns+=("artifact/sd-${BRANCH}-${SHORT}-windows-cuda-${sm}-x64.zip")
done
cuda_patterns+=("artifact/sd-${BRANCH}-${SHORT}-ubuntu-cuda-sm_121-arm64.tar.xz")
check_family cuda "${cuda_patterns[@]}"
# TheRock nightly ROCm 7.14.0 (windows-latest-rocm + the 7.14.0 leg
# of ubuntu-latest-rocm). Kept separate from the HIP SDK line below
# since they're different SDKs, not just different platforms.
check_family rocm-therock \
"artifact/sd-${BRANCH}-${SHORT}-bin-win-rocm-7.14.0-x64.zip" \
"artifact/sd-${BRANCH}-${SHORT}-bin-Linux-Ubuntu-24.04-x86_64-rocm-7.14.0.zip"
# CPU: ubuntu-latest-cmake's filename embeds the runner's OS name/
# version via lsb_release, so it's wildcarded rather than pinned.
check_family cpu \
"artifact/sd-${BRANCH}-${SHORT}-bin-Linux-*-x86_64.zip" \
"artifact/sd-${BRANCH}-${SHORT}-bin-win-cpu-x64.zip"
# Vulkan: same runner as CPU, differs only in -DSD_VULKAN=ON.
# Only builds Linux since the runner doesn't have a dedicated Windows vulkan build.
check_family vulkan \
"artifact/sd-${BRANCH}-${SHORT}-bin-Linux-*-x86_64-vulkan.zip"
check_family metal \
"artifact/sd-${BRANCH}-${SHORT}-bin-Darwin-macOS-*-arm64.zip"
echo "missing=$total_missing" >> "$GITHUB_OUTPUT"
echo "summary=$summary" >> "$GITHUB_OUTPUT"
files=$(find ./artifact -maxdepth 1 \( -name '*.zip' -o -name '*.tar.xz' \) 2>/dev/null | wc -l)
# Never create or update a release with nothing to publish.
if [ "$files" -eq 0 ]; then
echo "::error::No release artifacts found in ./artifact — aborting before creating a release."
exit 1
fi
if [ "$total_missing" -gt 0 ]; then
echo "$total_missing artifact(s) missing ($summary) — continuing to publish the $files artifact(s) from complete families. The job will still fail below so the gap gets noticed."
else
echo "All expected artifacts present. Found $files artifact(s) ready to upload."
fi
- name: Determine release summary
id: release_summary
run: |
if [[ "${{ github.event_name }}" == "schedule" ]]; then
echo "value=Nightly release for ${{ github.sha }}" >> "$GITHUB_OUTPUT"
else
echo "value=$(git log -1 --pretty=%s)" >> "$GITHUB_OUTPUT"
fi
# Get the release for this tag, creating it if it does not exist yet.
# This is idempotent: if a previous run created the release but failed
# partway through uploading (e.g. a transient GitHub outage), re-running
# reuses the existing release so the upload step below can fill the gaps.
- name: Create or get release
id: release
uses: actions/github-script@v8
env:
TAG_NAME: ${{ steps.tag_name.outputs.name }}
RELEASE_SUMMARY: ${{ steps.release_summary.outputs.value }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const tag = process.env.TAG_NAME;
const { owner, repo } = context.repo;
let release;
try {
release = (await github.rest.repos.getReleaseByTag({ owner, repo, tag })).data;
core.info(`Reusing existing release ${tag} (id ${release.id})`);
} catch (e) {
if (e.status !== 404) throw e;
try {
release = (await github.rest.repos.createRelease({ owner, repo, tag_name: tag, body: process.env.RELEASE_SUMMARY })).data;
core.info(`Created release ${tag} (id ${release.id})`);
} catch (createError) {
if (createError.status !== 422) throw createError;
release = (await github.rest.repos.getReleaseByTag({ owner, repo, tag })).data;
core.info(`Reusing concurrently created release ${tag} (id ${release.id})`);
}
}
core.setOutput('id', release.id);
- name: Upload release
id: upload_release
uses: actions/github-script@v8
env:
RELEASE_ID: ${{ steps.release.outputs.id }}
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const fs = require('fs');
const { owner, repo } = context.repo;
const release_id = Number(process.env.RELEASE_ID);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Retry transient failures (network errors, 5xx, rate limits) with
// exponential backoff so a momentary GitHub blip does not abort the
// whole upload and leave the release partially populated.
async function withRetry(label, fn, attempts = 5) {
for (let i = 1; ; i++) {
try {
return await fn();
} catch (e) {
const transient = !e.status || e.status >= 500 || e.status === 429;
if (i >= attempts || !transient) throw e;
const delay = Math.min(30000, 1000 * 2 ** (i - 1));
core.warning(`${label} failed (attempt ${i}/${attempts}): ${e.message}. Retrying in ${delay}ms`);
await sleep(delay);
}
}
}
const files = fs.readdirSync('./artifact').filter((f) => f.endsWith('.zip') || f.endsWith('.tar.xz'));
const wanted = new Set(files);
// Reconcile existing release assets against what this run decided to
// publish: drop anything not fully uploaded (a broken partial upload)
// and anything not in the current file set. The latter case matters
// because the release is reused across runs against the same tag —
// if a family was complete and published in an earlier run but this
// run finds it incomplete and drops it locally, its assets from that
// earlier run would otherwise linger and make the family look
// complete even though this run couldn't reproduce it.
const existing = new Set();
for (const a of await github.paginate(github.rest.repos.listReleaseAssets, { owner, repo, release_id })) {
if (a.state === 'uploaded' && wanted.has(a.name)) {
existing.add(a.name);
} else {
const reason = a.state !== 'uploaded' ? `incomplete (${a.state})` : 'stale (not part of this run)';
core.warning(`deleting ${reason} asset ${a.name}`);
await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id: a.id });
}
}
let uploaded = 0;
let skipped = 0;
for (const file of files) {
if (existing.has(file)) {
core.info(`skipping ${file} (already uploaded)`);
skipped++;
continue;
}
const data = fs.readFileSync(`./artifact/${file}`);
await withRetry(`upload ${file}`, () =>
github.rest.repos.uploadReleaseAsset({ owner, repo, release_id, name: file, data })
.catch(async (e) => {
if (e.status !== 422) throw e;
const assets = await github.paginate(github.rest.repos.listReleaseAssets, { owner, repo, release_id });
const asset = assets.find((a) => a.name === file);
if (asset?.state === 'uploaded') {
core.info(`accepting ${file} (already uploaded)`);
return;
}
if (asset) {
core.warning(`deleting conflicting asset ${file} (${asset.state})`);
await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id: asset.id });
throw new Error(`retrying upload for ${file} after deleting conflicting asset`);
}
throw e;
}));
core.info(`uploaded ${file}`);
uploaded++;
}
core.info(`Done: ${uploaded} uploaded, ${skipped} already present, ${files.length} total.`);
- name: Fail if any artifacts were missing
if: steps.check_artifacts.outputs.missing != '0'
run: |
echo "::error::${{ steps.check_artifacts.outputs.missing }} artifact(s) were missing (${{ steps.check_artifacts.outputs.summary }}) — the incomplete families were dropped from this release; other families were still published. See the 'Check release artifacts' step for the full per-file list."
exit 1