Skip to content

Deploy SDK

Deploy SDK #45

Workflow file for this run

name: Deploy SDK
on:
push:
tags:
- 'v*'
- 'release/*'
- 'release-*'
branches:
- main
- opengl
- develop
workflow_dispatch:
inputs:
tag_name:
description: "Release tag / version name (e.g. release/1.0.0 or v1.0.0)"
required: false
default: ""
type: string
create_release:
description: "Publish as official GitHub Release?"
required: true
default: true
type: boolean
is_draft:
description: "Publish as Draft Release?"
required: false
default: false
type: boolean
is_prerelease:
description: "Mark as Pre-release?"
required: false
default: false
type: boolean
concurrency:
group: deploy-sdk-${{ github.ref }}
cancel-in-progress: true
env:
CI: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_NOLOGO: true
NODE_OPTIONS: "--no-deprecation"
jobs:
build:
name: Build SDK (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux
preset: linux-clang
config: Release
- os: windows-latest
platform: windows
preset: windows-clang
config: Release
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 1
- name: Write submodule SHA manifest
run: git submodule status | tee submodule-shas.txt
- name: Cache submodules
uses: actions/cache@v4
with:
path: |
.git/modules
thirdparty
key: submodules-${{ runner.os }}-${{ hashFiles('submodule-shas.txt') }}
restore-keys: |
submodules-${{ runner.os }}-
- name: Init submodules
run: git submodule update --init --recursive --depth 1
- name: Setup MSYS2 (Windows)
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: false
path-type: inherit
install: mingw-w64-x86_64-clang lld mingw-w64-x86_64-lld mingw-w64-x86_64-gcc mingw-w64-x86_64-make
- name: Prefer MSYS2 toolchain over preinstalled MinGW
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: |
cygpath -w /mingw64/bin >> "$GITHUB_PATH"
- name: Setup Mold Linker (Linux)
if: matrix.os == 'ubuntu-latest'
uses: rui314/setup-mold@v1
- name: Install Linux Dependencies
if: matrix.os == 'ubuntu-latest'
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
xorg-dev \
libgl1-mesa-dev libglu1-mesa-dev \
libasound2-dev \
pkg-config libgtk-3-dev libdrm-dev libgbm-dev \
xvfb libxkbcommon-x11-0 libgl1-mesa-dri mesa-utils \
clang-18 clang++-18 libc++-18-dev libc++abi-18-dev lld-18 llvm-18
echo "CC=clang-18" >> $GITHUB_ENV
echo "CXX=clang++-18" >> $GITHUB_ENV
echo "LDFLAGS=-fuse-ld=mold" >> $GITHUB_ENV
- name: Get CMake and Ninja
uses: lukka/get-cmake@latest
- name: Setup Compiler Cache
uses: hendrikmuhs/ccache-action@v1.2
with:
variant: ${{ matrix.os == 'windows-latest' && 'sccache' || 'ccache' }}
key: SDK-${{ matrix.preset }}-${{ matrix.config }}
max-size: 5G
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install jinja2
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.0.x
10.0.x
- name: Get current version/date
id: info
shell: bash
run: |
echo "date=$(date +'%d.%m.%y')" >> $GITHUB_OUTPUT
if [[ -n "${{ inputs.tag_name }}" ]]; then
RAW_TAG="${{ inputs.tag_name }}"
TAG_VERSION="${RAW_TAG#release/}"
TAG_VERSION="${TAG_VERSION#release-}"
TAG_VERSION="${TAG_VERSION#v}"
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
RAW_TAG="${GITHUB_REF#refs/tags/}"
TAG_VERSION="${RAW_TAG#release/}"
TAG_VERSION="${TAG_VERSION#release-}"
TAG_VERSION="${TAG_VERSION#v}"
else
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
CLEAN_BRANCH="${BRANCH_NAME//\//-}"
SHORT_SHA="${GITHUB_SHA:0:7}"
TAG_VERSION="${CLEAN_BRANCH}-${SHORT_SHA}"
RAW_TAG="v${TAG_VERSION}"
fi
echo "raw_tag=${RAW_TAG}" >> $GITHUB_OUTPUT
echo "version=${TAG_VERSION}" >> $GITHUB_OUTPUT
- name: Configure CMake
run: |
set -euo pipefail
PYTHON_EXE=$(python3 -c "import sys; print(sys.executable)" 2>/dev/null || python -c "import sys; print(sys.executable)")
launcher="sccache"
if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
launcher="ccache"
fi
mkdir -p "build/${{ matrix.preset }}"
cmake --preset ${{ matrix.preset }} \
-DCMAKE_C_COMPILER_LAUNCHER=$launcher \
-DCMAKE_CXX_COMPILER_LAUNCHER=$launcher \
-DPython_EXECUTABLE="${PYTHON_EXE}" \
-DPython3_EXECUTABLE="${PYTHON_EXE}" \
-DCH_CI=ON \
-DENABLE_UNITY_BUILD=OFF \
-DENABLE_PCH=OFF \
-DBUILD_TESTS=OFF \
2>&1 | tee "build/${{ matrix.preset }}/configure.log"
- name: Build binaries
run: |
set -euo pipefail
cmake --build "build/${{ matrix.preset }}" --config ${{ matrix.config }} --parallel \
2>&1 | tee "build/${{ matrix.preset }}/build_${{ matrix.config }}.log"
- name: Sync resources
run: |
set -euo pipefail
python tools/sync_resources.py --root . --bin "build/${{ matrix.preset }}/bin" --config ${{ matrix.config }}
- name: Pack game resources into .pack
run: |
set -euo pipefail
WS="${{ github.workspace }}"
BIN_DIR="$WS/build/${{ matrix.preset }}/bin/${{ matrix.config }}"
if [[ ! -d "$BIN_DIR" ]]; then
BIN_DIR="$WS/build/${{ matrix.preset }}/bin"
fi
# Find packer executable (multi-config or single-config)
PACKER="$BIN_DIR/packer"
[[ -f "$PACKER.exe" ]] && PACKER="$PACKER.exe"
if [[ ! -f "$PACKER" ]]; then
# Try searching the whole build tree
PACKER=$(find "$WS/build/${{ matrix.preset }}" -name "packer" -o -name "packer.exe" 2>/dev/null | head -1)
fi
if [[ ! -f "$PACKER" ]]; then
echo "ERROR: packer executable not found" >&2
exit 1
fi
echo "Using packer: $PACKER"
# Find .chproject file for the active game
CHPROJECT=""
for f in "$WS/game/chaineddecos"/*.chproject; do
[[ -f "$f" ]] && CHPROJECT="$f" && break
done
# Create resources.pack from assets/ + resources/
python "$WS/tools/create_pack.py" \
--packer "$PACKER" \
--output "$BIN_DIR" \
${CHPROJECT:+--chproject "$CHPROJECT"} \
--assets "$WS/game/chaineddecos/assets" \
--resources "$WS/resources" \
--split-mb 0 \
--zip-threshold 5 \
--data-version 0
- name: Package Game and Editor bundles
id: package
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.info.outputs.version }}"
PLATFORM="${{ matrix.platform }}"
IS_WINDOWS="${{ matrix.os == 'windows-latest' }}"
WS="${{ github.workspace }}"
# Locate the build output directory (multi-config vs single-config generators)
BIN_DIR="$WS/build/${{ matrix.preset }}/bin/${{ matrix.config }}"
if [[ ! -d "$BIN_DIR" ]]; then
BIN_DIR="$WS/build/${{ matrix.preset }}/bin"
fi
# Collect runtime DLLs / shared libs needed by both executables
RUNTIME_FILES=()
for f in "$BIN_DIR"/*.dll "$BIN_DIR"/*.so "$BIN_DIR"/*.so.* "$BIN_DIR"/*.dylib; do
[[ -f "$f" ]] && RUNTIME_FILES+=("$f")
done
# Helper: assemble a bundle directory and compress it
make_bundle() {
local BUNDLE_NAME="$1" # e.g. ChainedDecos-Game
local BUNDLE_DIR="$WS/pkg-$BUNDLE_NAME"
shift
mkdir -p "$BUNDLE_DIR"
# Copy files/dirs passed as remaining arguments
while (( "$#" )); do
local SRC="$1"; shift
if [[ -e "$SRC" ]]; then
cp -r "$SRC" "$BUNDLE_DIR/"
fi
done
# Copy runtime DLLs / shared libs
for f in "${RUNTIME_FILES[@]:-}"; do
[[ -f "$f" ]] && cp "$f" "$BUNDLE_DIR/"
done
# Copy .NET managed assemblies (Coral + Chained scripting)
for f in "$BIN_DIR"/*.dll "$BIN_DIR"/*.runtimeconfig.json "$BIN_DIR"/*.deps.json; do
[[ -f "$f" ]] && cp "$f" "$BUNDLE_DIR/" 2>/dev/null || true
done
local ARCHIVE_PATH="$WS/${BUNDLE_NAME}-${VERSION}-${PLATFORM}"
if [[ "$IS_WINDOWS" == "true" ]]; then
pushd "$BUNDLE_DIR" > /dev/null
powershell -Command "Compress-Archive -Path * -DestinationPath '${ARCHIVE_PATH}.zip' -Force"
popd > /dev/null
echo "${BUNDLE_NAME}-${VERSION}-${PLATFORM}.zip"
else
pushd "$BUNDLE_DIR" > /dev/null
tar -czf "${ARCHIVE_PATH}.tar.gz" .
popd > /dev/null
echo "${BUNDLE_NAME}-${VERSION}-${PLATFORM}.tar.gz"
fi
}
# ── Game bundle: executable + .pack + scripts/ ─────────────────────
GAME_EXE=""
[[ -f "$BIN_DIR/ChainedDecos.exe" ]] && GAME_EXE="$BIN_DIR/ChainedDecos.exe"
[[ -f "$BIN_DIR/ChainedDecos" ]] && GAME_EXE="$BIN_DIR/ChainedDecos"
# Collect .pack files created by create_pack.py
GAME_PACKS=()
for f in "$BIN_DIR"/resources*.pack; do
[[ -f "$f" ]] && GAME_PACKS+=("$f")
done
# Locate .chproject for the game
CHPROJECT=""
for f in "$WS/game/chaineddecos"/*.chproject; do
[[ -f "$f" ]] && CHPROJECT="$f" && break
done
GAME_ARCHIVE=$(make_bundle "ChainedDecos-Game" \
"$GAME_EXE" \
"${GAME_PACKS[@]}" \
${CHPROJECT:+"$CHPROJECT"} \
"$BIN_DIR/scripts" \
"$BIN_DIR/nethost")
echo "game_archive=$GAME_ARCHIVE" >> $GITHUB_OUTPUT
# ── Editor bundle: executable + engine resources only (no game assets) ──
EDITOR_EXE=""
[[ -f "$BIN_DIR/ChainedEditor.exe" ]] && EDITOR_EXE="$BIN_DIR/ChainedEditor.exe"
[[ -f "$BIN_DIR/ChainedEditor" ]] && EDITOR_EXE="$BIN_DIR/ChainedEditor"
EDITOR_ARCHIVE=$(make_bundle "ChainedEditor" \
"$EDITOR_EXE" \
"$BIN_DIR/resources" \
"$BIN_DIR/editor_settings.yaml" \
"$BIN_DIR/nethost")
echo "editor_archive=$EDITOR_ARCHIVE" >> $GITHUB_OUTPUT
- name: Upload Game artifact
uses: actions/upload-artifact@v4
with:
name: Game-${{ matrix.platform }}
path: ${{ github.workspace }}/${{ steps.package.outputs.game_archive }}
retention-days: 30
- name: Upload Editor artifact
uses: actions/upload-artifact@v4
with:
name: Editor-${{ matrix.platform }}
path: ${{ github.workspace }}/${{ steps.package.outputs.editor_archive }}
retention-days: 30
release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: build
if: >-
(github.event_name == 'workflow_dispatch' && (inputs.create_release == true || inputs.create_release == 'true')) ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get release info
id: info
run: |
echo "date=$(date +'%d.%m.%y')" >> $GITHUB_OUTPUT
if [[ -n "${{ inputs.tag_name }}" ]]; then
RAW_TAG="${{ inputs.tag_name }}"
TAG_VERSION="${RAW_TAG#release/}"
TAG_VERSION="${TAG_VERSION#release-}"
TAG_VERSION="${TAG_VERSION#v}"
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
RAW_TAG="${GITHUB_REF#refs/tags/}"
TAG_VERSION="${RAW_TAG#release/}"
TAG_VERSION="${TAG_VERSION#release-}"
TAG_VERSION="${TAG_VERSION#v}"
else
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
CLEAN_BRANCH="${BRANCH_NAME//\//-}"
SHORT_SHA="${GITHUB_SHA:0:7}"
TAG_VERSION="${CLEAN_BRANCH}-${SHORT_SHA}"
RAW_TAG="v${TAG_VERSION}"
fi
echo "raw_tag=${RAW_TAG}" >> $GITHUB_OUTPUT
echo "version=${TAG_VERSION}" >> $GITHUB_OUTPUT
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
pattern: "{Game,Editor}-*"
merge-multiple: true
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: "Chained Engine v${{ steps.info.outputs.version }}"
tag_name: "${{ steps.info.outputs.raw_tag }}"
files: release-assets/*
body: |
### Chained Engine v${{ steps.info.outputs.version }}
- **Build Date**: ${{ steps.info.outputs.date }}
- **Platforms**: Linux (clang), Windows (clang)
#### Downloads
| File | Platform | Contents |
|------|----------|----------|
| `ChainedDecos-Game-*-windows.zip` | Windows | Game executable + .chproject + resources.pack + scripts + nethost/ + DLLs |
| `ChainedDecos-Game-*-linux.tar.gz` | Linux | Game executable + .chproject + resources.pack + scripts + nethost/ |
| `ChainedEditor-*-windows.zip` | Windows | Editor executable + engine resources + nethost/ + DLLs |
| `ChainedEditor-*-linux.tar.gz` | Linux | Editor executable + engine resources + nethost/ |
> **Game** — packaged with `resources.pack` (assets + engine resources compressed), `.chproject` project config, `scripts/` (C# assemblies), and `nethost/` (.NET hosting). On Linux, requires .NET 9+ runtime.
> **Editor** — includes the editor binary, engine resources (shaders, fonts, icons), and `nethost/` (.NET hosting). On Linux, requires .NET 9+ runtime.
#### Requirements
- Windows: no extra dependencies (all DLLs included)
- Linux: requires .NET 9+ runtime for C# scripting
draft: ${{ inputs.is_draft || false }}
prerelease: ${{ inputs.is_prerelease || false }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}