0.1.6 dev #52
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*" | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag Name" | |
| required: false | |
| type: string | |
| prerelease: | |
| description: "Mark as pre-release." | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| env: | |
| JAVA_VERSION: "21" | |
| APP_NAME: Tritium | |
| MAIN_CLASS: io.github.tritium_launcher.launcher.Main | |
| WINGET_PACKAGE_IDENTIFIER: TritiumLauncher.Tritium | |
| WINDOWS_UPGRADE_UUID: "36525b46-a7ad-4ff2-8be7-b760908f7d83" | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.platform }}-${{ matrix.arch }} | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-24.04 | |
| platform: linux | |
| arch: x64 | |
| - runner: windows-2025 | |
| platform: windows | |
| arch: x64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Set Up Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Set Up Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| - name: Resolve App Version | |
| id: version | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| DISPATCH_TAG: ${{ github.event.inputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| raw="" | |
| if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then | |
| raw="${GITHUB_REF#refs/tags/v}" | |
| fi | |
| if [[ -z "$raw" && "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" && -n "${DISPATCH_TAG:-}" ]]; then | |
| raw="${DISPATCH_TAG#v}" | |
| fi | |
| if [[ -z "$raw" ]]; then | |
| raw="$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' build.gradle.kts | head -n1 || true)" | |
| fi | |
| cleaned="$(echo "$raw" | sed -E 's/[^0-9.]+/./g; s/\.+/./g; s/^\.//; s/\.$//')" | |
| if [[ -z "$cleaned" ]]; then | |
| cleaned="0.1" | |
| fi | |
| IFS='.' read -r -a parts <<< "$cleaned" | |
| major="${parts[0]:-0}" | |
| minor="${parts[1]:-0}" | |
| build_from_source="${parts[2]:-}" | |
| major_num="$((10#$major))" | |
| minor_num="$((10#$minor))" | |
| is_release_build=false | |
| if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then | |
| is_release_build=true | |
| fi | |
| if [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" && -n "${DISPATCH_TAG:-}" ]]; then | |
| is_release_build=true | |
| fi | |
| next_series_build() { | |
| local major_series="$1" | |
| local minor_series="$2" | |
| local page=1 | |
| local max_seen=0 | |
| while :; do | |
| local payload | |
| if ! payload="$(curl -fsSL \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases?per_page=100&page=${page}")"; then | |
| break | |
| fi | |
| local page_count | |
| page_count="$(echo "$payload" | sed -nE 's/^[[:space:]]*"tag_name":[[:space:]]*"([^"]+)".*/\1/p' | wc -l | tr -d '[:space:]')" | |
| while IFS= read -r tag; do | |
| if [[ "$tag" =~ ^v${major_series}\.${minor_series}\.([0-9]+)($|[^0-9].*) ]]; then | |
| local build_patch="${BASH_REMATCH[1]}" | |
| local build_patch_num="$((10#$build_patch))" | |
| if [[ "$build_patch_num" -gt "$max_seen" ]]; then | |
| max_seen="$build_patch_num" | |
| fi | |
| fi | |
| done < <(echo "$payload" | sed -nE 's/^[[:space:]]*"tag_name":[[:space:]]*"([^"]+)".*/\1/p') | |
| if [[ ! "$page_count" =~ ^[0-9]+$ || "$page_count" -lt 100 ]]; then | |
| break | |
| fi | |
| page=$((page + 1)) | |
| done | |
| echo "$((max_seen + 1))" | |
| } | |
| build_number="" | |
| if [[ "$is_release_build" == "true" ]]; then | |
| if [[ "$build_from_source" =~ ^[0-9]+$ ]]; then | |
| build_number="$((10#$build_from_source))" | |
| else | |
| build_number="$(next_series_build "$major_num" "$minor_num")" | |
| fi | |
| elif [[ "$build_from_source" =~ ^[0-9]+$ ]]; then | |
| build_number="$((10#$build_from_source))" | |
| else | |
| build_number="${GITHUB_RUN_NUMBER:-0}" | |
| fi | |
| build_num="$((10#$build_number))" | |
| app_version="${major_num}.${minor_num}.${build_num}" | |
| echo "app_version=$app_version" >> "$GITHUB_OUTPUT" | |
| echo "Resolved app version: $app_version" | |
| - name: Resolve Qt Version | |
| id: qtver | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| qt_version="$(sed -nE 's/^qt[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' gradle/libs.versions.toml | head -n1 || true)" | |
| if [[ -z "$qt_version" ]]; then | |
| echo "Failed to resolve Qt version from gradle/libs.versions.toml" >&2 | |
| exit 1 | |
| fi | |
| echo "value=$qt_version" >> "$GITHUB_OUTPUT" | |
| echo "Resolved Qt version: $qt_version" | |
| - name: Prepare Package Input (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: .\gradlew.bat --no-daemon clean preparePackageInput | |
| - name: Prepare Package Input (Linux) | |
| if: runner.os == 'Linux' | |
| run: ./gradlew --no-daemon clean preparePackageInput | |
| - name: Bundle Qt Libraries (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| QT_VERSION: ${{ steps.qtver.outputs.value }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| python -m pip install --upgrade pip | |
| python -m pip install --upgrade aqtinstall | |
| $qtHost = "windows" | |
| $archOutput = & python -m aqt list-qt $qtHost desktop --arch $env:QT_VERSION 2>&1 | |
| if ($LASTEXITCODE -ne 0) { throw "list-qt --arch failed" } | |
| $archs = ($archOutput -join " ") -split '\s+' | Where-Object { $_ -ne '' } | |
| $qtArch = $archs | Where-Object { $_ -match 'msvc' -and $_ -match '64' } | Select-Object -First 1 | |
| if (-not $qtArch) { $qtArch = $archs | Where-Object { $_ -match '64' } | Select-Object -First 1 } | |
| if (-not $qtArch) { throw "Unable to resolve Qt architecture. Available: $($archs -join ', ')" } | |
| $qtPlatform = "windows-x64" | |
| $modulesOutput = & python -m aqt list-qt $qtHost desktop --modules $env:QT_VERSION $qtArch 2>&1 | |
| if ($LASTEXITCODE -ne 0) { throw "list-qt --modules failed" } | |
| if ($modulesOutput -match '\bqtsvg\b') { | |
| Write-Host "Installing Qt with optional module qtsvg" | |
| & python -m aqt install-qt $qtHost desktop $env:QT_VERSION $qtArch -m qtsvg -O "$PWD\\.qt" | |
| if ($LASTEXITCODE -ne 0) { throw "install-qt (with qtsvg) failed" } | |
| } else { | |
| Write-Host "qtsvg module not available; installing base Qt only" | |
| & python -m aqt install-qt $qtHost desktop $env:QT_VERSION $qtArch -O "$PWD\\.qt" | |
| if ($LASTEXITCODE -ne 0) { throw "install-qt failed" } | |
| } | |
| $qtRoot = "$PWD\\.qt\\$env:QT_VERSION" | |
| $qtDir = Join-Path $qtRoot $qtArch | |
| $qtLibDir = Join-Path $qtDir "bin" | |
| if (-not (Test-Path $qtLibDir)) { | |
| $candidate = Get-ChildItem -Path $qtRoot -Directory -ErrorAction SilentlyContinue | | |
| Where-Object { Test-Path (Join-Path $_.FullName "bin") } | | |
| Select-Object -First 1 | |
| if ($candidate) { | |
| $qtDir = $candidate.FullName | |
| $qtLibDir = Join-Path $qtDir "bin" | |
| } | |
| } | |
| if (-not (Test-Path $qtLibDir)) { | |
| $dirs = Get-ChildItem -Path $qtRoot -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName | |
| throw "Qt library path not found under $qtRoot (initial: $qtArch). Candidates: $($dirs -join ', ')" | |
| } | |
| Write-Host "Resolved Qt directory: $qtDir" | |
| $toolsDir = "$PWD\\.qtjambi-deployer" | |
| New-Item -ItemType Directory -Force $toolsDir | Out-Null | |
| $base = "https://repo1.maven.org/maven2/io/qtjambi" | |
| Invoke-WebRequest "$base/qtjambi/$env:QT_VERSION/qtjambi-$env:QT_VERSION.jar" -OutFile "$toolsDir\\qtjambi-$env:QT_VERSION.jar" | |
| Invoke-WebRequest "$base/qtjambi-deployer/$env:QT_VERSION/qtjambi-deployer-$env:QT_VERSION.jar" -OutFile "$toolsDir\\qtjambi-deployer-$env:QT_VERSION.jar" | |
| Invoke-WebRequest "$base/qtjambi-native-$qtPlatform/$env:QT_VERSION/qtjambi-native-$qtPlatform-$env:QT_VERSION.jar" -OutFile "$toolsDir\\qtjambi-native-$qtPlatform-$env:QT_VERSION.jar" | |
| $cp = "$toolsDir\\qtjambi-deployer-$env:QT_VERSION.jar;$toolsDir\\qtjambi-$env:QT_VERSION.jar;$toolsDir\\qtjambi-native-$qtPlatform-$env:QT_VERSION.jar" | |
| java "-Djava.library.path=$qtLibDir" -cp $cp io.qt.qtjambi.deployer.Main qt "--qtdir=$qtDir" "--platform=$qtPlatform" "--target-version=$env:QT_VERSION" -d "$PWD\\build\\package-input" | |
| Get-ChildItem "$PWD\\build\\package-input\\qt-lib-*.jar" | ForEach-Object { Write-Host "Bundled: $($_.Name)" } | |
| - name: Bundle Qt Libraries (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| env: | |
| QT_VERSION: ${{ steps.qtver.outputs.value }} | |
| run: | | |
| set -euo pipefail | |
| python3 -m pip install --user --break-system-packages --upgrade pip aqtinstall | |
| host="linux" | |
| qt_platform="linux-x64" | |
| arch_pattern='(linux_)?gcc_64' | |
| if ! archs="$(python3 -m aqt list-qt "$host" desktop --arch "$QT_VERSION" | tr ' \r\t' '\n' | sed '/^$/d')"; then | |
| echo "Failed to query Qt architectures for host '$host'." >&2 | |
| exit 1 | |
| fi | |
| qt_arch="$(echo "$archs" | grep -E "$arch_pattern" | head -n1 || true)" | |
| if [[ -z "$qt_arch" ]]; then | |
| echo "Unable to resolve Qt architecture for linux/x64." >&2 | |
| echo "$archs" >&2 | |
| exit 1 | |
| fi | |
| modules="$(python3 -m aqt list-qt "$host" desktop --modules "$QT_VERSION" "$qt_arch" | tr ' \r\t' '\n' | sed '/^$/d' || true)" | |
| if echo "$modules" | grep -qx "qtsvg"; then | |
| echo "Installing Qt with optional module qtsvg" | |
| python3 -m aqt install-qt "$host" desktop "$QT_VERSION" "$qt_arch" -m qtsvg -O "$PWD/.qt" | |
| else | |
| echo "qtsvg module not available for $host/$qt_arch on Qt $QT_VERSION; installing base Qt only" | |
| python3 -m aqt install-qt "$host" desktop "$QT_VERSION" "$qt_arch" -O "$PWD/.qt" | |
| fi | |
| qt_root="$PWD/.qt/$QT_VERSION" | |
| qt_dir="$qt_root/$qt_arch" | |
| if [[ ! -d "$qt_dir/lib" ]]; then | |
| qt_dir="$(find "$qt_root" -mindepth 1 -maxdepth 2 -type d | while read -r d; do [[ -d "$d/lib" ]] && { echo "$d"; break; }; done)" | |
| fi | |
| qt_lib_dir="$qt_dir/lib" | |
| if [[ ! -d "$qt_lib_dir" ]]; then | |
| echo "Qt library path not found under: $qt_root (initial: $qt_arch)" >&2 | |
| find "$qt_root" -maxdepth 3 -type d | sed 's/^/ /' >&2 || true | |
| exit 1 | |
| fi | |
| echo "Resolved Qt directory: $qt_dir" | |
| tools_dir="$PWD/.qtjambi-deployer" | |
| mkdir -p "$tools_dir" | |
| base="https://repo1.maven.org/maven2/io/qtjambi" | |
| curl -fsSL -o "$tools_dir/qtjambi-$QT_VERSION.jar" "$base/qtjambi/$QT_VERSION/qtjambi-$QT_VERSION.jar" | |
| curl -fsSL -o "$tools_dir/qtjambi-deployer-$QT_VERSION.jar" "$base/qtjambi-deployer/$QT_VERSION/qtjambi-deployer-$QT_VERSION.jar" | |
| curl -fsSL -o "$tools_dir/qtjambi-native-$qt_platform-$QT_VERSION.jar" "$base/qtjambi-native-$qt_platform/$QT_VERSION/qtjambi-native-$qt_platform-$QT_VERSION.jar" | |
| java -Djava.library.path="$qt_lib_dir" \ | |
| -cp "$tools_dir/qtjambi-deployer-$QT_VERSION.jar:$tools_dir/qtjambi-$QT_VERSION.jar:$tools_dir/qtjambi-native-$qt_platform-$QT_VERSION.jar" \ | |
| io.qt.qtjambi.deployer.Main qt \ | |
| --qtdir="$qt_dir" \ | |
| --platform="$qt_platform" \ | |
| --target-version="$QT_VERSION" \ | |
| -d "$PWD/build/package-input" | |
| ls -1 build/package-input/qt-lib-*.jar | |
| - name: Prepare Dist | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| - name: Package Windows Bundles | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force package | Out-Null | |
| jpackage ` | |
| --type app-image ` | |
| --name "${{ env.APP_NAME }}" ` | |
| --vendor "FooterManDev" ` | |
| --input build/package-input ` | |
| --main-jar tritium-app.jar ` | |
| --main-class "${{ env.MAIN_CLASS }}" ` | |
| --app-version "${{ steps.version.outputs.app_version }}" ` | |
| --java-options "-Dio.qt.deploymentdir=user" ` | |
| --icon "packaging/windows/jpackage/${{ env.APP_NAME }}.ico" ` | |
| --dest package | |
| $cfgFile = Get-ChildItem -Path "package" -Recurse -Filter "${{ env.APP_NAME }}.cfg" | Select-Object -First 1 | |
| if (-not $cfgFile) { | |
| throw "Launcher config file not found in generated Windows app-image." | |
| } | |
| $appDir = $cfgFile.DirectoryName | |
| $bundledQtLibJars = Get-ChildItem -Path "build/package-input" -Filter "qt-lib-*.jar" -ErrorAction SilentlyContinue | Sort-Object Name | |
| if ($bundledQtLibJars.Count -eq 0) { | |
| throw "No qt-lib-*.jar files were generated in build/package-input; Qt deployment is incomplete." | |
| } | |
| $appQtLibJars = Get-ChildItem -Path $appDir -Filter "qt-lib-*.jar" -ErrorAction SilentlyContinue | Sort-Object Name | |
| if ($appQtLibJars.Count -eq 0) { | |
| throw "Generated Windows app-image is missing qt-lib-*.jar files in $appDir." | |
| } | |
| $nativeQtJambiJars = Get-ChildItem -Path $appDir -Filter "qtjambi-native-*.jar" -ErrorAction SilentlyContinue | Sort-Object Name | |
| if ($nativeQtJambiJars.Count -eq 0) { | |
| throw "Generated Windows app-image is missing qtjambi-native-*.jar files in $appDir." | |
| } | |
| $cfgClasspathLines = Get-Content $cfgFile.FullName | Where-Object { $_ -match '^app\.classpath=' } | |
| if (-not ($cfgClasspathLines | Where-Object { $_ -match 'qt-lib-' }) -or -not ($cfgClasspathLines | Where-Object { $_ -match 'qtjambi-native-' })) { | |
| throw "Generated Windows launcher config does not reference the required QtJambi runtime jars: $($cfgFile.FullName)" | |
| } | |
| $cfgClasspathLines | ForEach-Object { Write-Host "Launcher cfg: $($_)" } | |
| $nativeQtJambiJars | ForEach-Object { Write-Host "App-image includes QtJambi native jar: $($_.Name)" } | |
| $appQtLibJars | ForEach-Object { Write-Host "App-image includes Qt jar: $($_.Name)" } | |
| New-Item -ItemType Directory -Force "package/${{ env.APP_NAME }}/licenses" | Out-Null | |
| Copy-Item "LICENSE" "package/${{ env.APP_NAME }}/" | |
| Copy-Item "THIRD_PARTY_NOTICES.md" "package/${{ env.APP_NAME }}/" | |
| Copy-Item "third_party/licenses/*" "package/${{ env.APP_NAME }}/licenses/" | |
| Compress-Archive -Path "package/${{ env.APP_NAME }}/*" -DestinationPath "dist/tritium-${{ matrix.platform }}-${{ matrix.arch }}.zip" -Force | |
| jpackage ` | |
| --type msi ` | |
| --name "${{ env.APP_NAME }}" ` | |
| --vendor "FooterManDev" ` | |
| --app-version "${{ steps.version.outputs.app_version }}" ` | |
| --app-image "package/${{ env.APP_NAME }}" ` | |
| --icon "packaging/windows/jpackage/${{ env.APP_NAME }}.ico" ` | |
| --win-menu ` | |
| --win-shortcut ` | |
| --win-dir-chooser ` | |
| --win-per-user-install ` | |
| --win-upgrade-uuid "${{ env.WINDOWS_UPGRADE_UUID }}" ` | |
| --dest dist | |
| $msi = Get-ChildItem -Path dist -Filter "*.msi" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | |
| if (-not $msi) { | |
| throw "Failed to find generated MSI in dist/." | |
| } | |
| Move-Item -Path $msi.FullName -Destination "dist/tritium-${{ matrix.platform }}-${{ matrix.arch }}.msi" -Force | |
| - name: Package Linux Portable Bundles | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p package | |
| jpackage \ | |
| --type app-image \ | |
| --name "${{ env.APP_NAME }}" \ | |
| --input build/package-input \ | |
| --main-jar tritium-app.jar \ | |
| --main-class "${{ env.MAIN_CLASS }}" \ | |
| --app-version "${{ steps.version.outputs.app_version }}" \ | |
| --dest package | |
| cfg_file="$(find package -type f -name "${{ env.APP_NAME }}.cfg" | head -n1 || true)" | |
| if [[ -z "$cfg_file" ]]; then | |
| echo "Launcher config file not found in generated Linux app-image." >&2 | |
| exit 1 | |
| fi | |
| app_dir="$(dirname "$cfg_file")" | |
| bundled_qt_lib_jars="$(find build/package-input -maxdepth 1 -type f -name 'qt-lib-*.jar' -exec basename {} \; | sort || true)" | |
| if [[ -z "$bundled_qt_lib_jars" ]]; then | |
| echo "No qt-lib-*.jar files were generated in build/package-input; Qt deployment is incomplete." >&2 | |
| exit 1 | |
| fi | |
| app_qt_lib_jars="$(find "$app_dir" -maxdepth 1 -type f -name 'qt-lib-*.jar' -exec basename {} \; | sort || true)" | |
| if [[ -z "$app_qt_lib_jars" ]]; then | |
| echo "Generated Linux app-image is missing qt-lib-*.jar files in $app_dir." >&2 | |
| exit 1 | |
| fi | |
| app_native_qtjambi_jars="$(find "$app_dir" -maxdepth 1 -type f -name 'qtjambi-native-*.jar' -exec basename {} \; | sort || true)" | |
| if [[ -z "$app_native_qtjambi_jars" ]]; then | |
| echo "Generated Linux app-image is missing qtjambi-native-*.jar files in $app_dir." >&2 | |
| exit 1 | |
| fi | |
| cfg_classpath_lines="$(grep '^app\.classpath=' "$cfg_file" || true)" | |
| if ! grep -q 'qt-lib-' <<< "$cfg_classpath_lines" || ! grep -q 'qtjambi-native-' <<< "$cfg_classpath_lines"; then | |
| echo "Generated Linux launcher config does not reference the required QtJambi runtime jars: $cfg_file" >&2 | |
| exit 1 | |
| fi | |
| printf '%s\n' "$cfg_classpath_lines" | sed 's/^/Launcher cfg: /' | |
| printf '%s\n' "$app_native_qtjambi_jars" | sed 's/^/App-image includes QtJambi native jar: /' | |
| printf '%s\n' "$app_qt_lib_jars" | sed 's/^/App-image includes Qt jar: /' | |
| mkdir -p "package/${{ env.APP_NAME }}/licenses" | |
| cp LICENSE "package/${{ env.APP_NAME }}/" | |
| cp THIRD_PARTY_NOTICES.md "package/${{ env.APP_NAME }}/" | |
| cp third_party/licenses/* "package/${{ env.APP_NAME }}/licenses/" | |
| tar -C package -czf "dist/tritium-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz" "${{ env.APP_NAME }}" | |
| appimage_arch="x86_64" | |
| if curl -fsSL -o appimagetool.AppImage \ | |
| "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${appimage_arch}.AppImage"; then | |
| chmod +x appimagetool.AppImage | |
| rm -rf AppDir | |
| mkdir -p AppDir | |
| cp -a "package/${{ env.APP_NAME }}/." AppDir/ | |
| printf '%s\n' \ | |
| '#!/bin/sh' \ | |
| 'HERE="$(dirname "$(readlink -f "$0")")"' \ | |
| 'exec "$HERE/bin/Tritium" "$@"' \ | |
| > AppDir/AppRun | |
| chmod +x AppDir/AppRun | |
| printf '%s\n' \ | |
| '[Desktop Entry]' \ | |
| 'Type=Application' \ | |
| 'Name=Tritium' \ | |
| 'Exec=Tritium' \ | |
| 'Icon=tritium' \ | |
| 'Categories=Development;' \ | |
| 'Terminal=false' \ | |
| > AppDir/Tritium.desktop | |
| cp src/main/resources/icons/tritium.png AppDir/tritium.png | |
| ln -sf tritium.png AppDir/.DirIcon | |
| ARCH="$appimage_arch" APPIMAGE_EXTRACT_AND_RUN=1 \ | |
| ./appimagetool.AppImage AppDir "dist/tritium-${{ matrix.platform }}-${{ matrix.arch }}.AppImage" | |
| chmod +x "dist/tritium-${{ matrix.platform }}-${{ matrix.arch }}.AppImage" | |
| else | |
| echo "AppImage tooling unavailable for $appimage_arch, skipping AppImage artifact." | |
| fi | |
| - name: Upload Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tritium-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: dist/* | |
| if-no-files-found: error | |
| release: | |
| name: Publish Release | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| tag_name: ${{ steps.tag.outputs.name }} | |
| permissions: | |
| contents: write | |
| needs: build | |
| if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch')) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Download Build Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-assets | |
| merge-multiple: true | |
| - name: Generate Checksums | |
| run: | | |
| set -euo pipefail | |
| cd release-assets | |
| sha256sum * > SHA256SUMS.txt | |
| - name: Resolve Release Tag | |
| id: tag | |
| shell: bash | |
| env: | |
| DISPATCH_TAG: ${{ github.event.inputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| raw="" | |
| if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then | |
| raw="${GITHUB_REF#refs/tags/v}" | |
| fi | |
| if [[ -z "$raw" && "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" && -n "${DISPATCH_TAG:-}" ]]; then | |
| raw="${DISPATCH_TAG#v}" | |
| fi | |
| if [[ -z "$raw" ]]; then | |
| raw="$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' build.gradle.kts | head -n1 || true)" | |
| fi | |
| numeric="$(echo "$raw" | sed -E 's/[^0-9.]+/./g; s/\.+/./g; s/^\.//; s/\.$//')" | |
| if [[ -z "$numeric" ]]; then | |
| echo "Unable to resolve numeric version from build.gradle.kts version='$raw'" >&2 | |
| exit 1 | |
| fi | |
| IFS='.' read -r -a parts <<< "$numeric" | |
| major="${parts[0]:-0}" | |
| minor="${parts[1]:-0}" | |
| patch="${parts[2]:-0}" | |
| major_num="$((10#$major))" | |
| minor_num="$((10#$minor))" | |
| patch_num="$((10#$patch))" | |
| echo "name=v${major_num}.${minor_num}.${patch_num}" >> "$GITHUB_OUTPUT" | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.name }} | |
| target_commitish: ${{ github.sha }} | |
| name: Tritium ${{ steps.tag.outputs.name }} | |
| prerelease: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.prerelease == 'true' }} | |
| generate_release_notes: true | |
| files: | | |
| release-assets/* | |
| aur: | |
| name: Update AUR Package | |
| runs-on: ubuntu-24.04 | |
| needs: release | |
| permissions: | |
| contents: read | |
| if: needs.release.result == 'success' && github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') && !(github.event_name == 'workflow_dispatch' && github.event.inputs.prerelease == 'true') | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Configure AUR SSH | |
| shell: bash | |
| env: | |
| AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| AUR_AUTOMATION_READY: ${{ vars.AUR_AUTOMATION_READY }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${AUR_AUTOMATION_READY:-}" != "true" ]]; then | |
| echo "AUR_AUTOMATION_READY is not true. Skipping AUR update." | |
| exit 0 | |
| fi | |
| if [[ -z "${AUR_SSH_PRIVATE_KEY:-}" ]]; then | |
| echo "AUR_SSH_PRIVATE_KEY is not set. Skipping AUR update." | |
| exit 0 | |
| fi | |
| install -dm700 ~/.ssh | |
| printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur | |
| chmod 600 ~/.ssh/aur | |
| ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts | |
| chmod 644 ~/.ssh/known_hosts | |
| - name: Sync AUR package files | |
| shell: bash | |
| env: | |
| AUR_AUTOMATION_READY: ${{ vars.AUR_AUTOMATION_READY }} | |
| TAG_NAME: ${{ needs.release.outputs.tag_name }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${AUR_AUTOMATION_READY:-}" != "true" ]]; then | |
| exit 0 | |
| fi | |
| if [[ -z "${TAG_NAME:-}" ]]; then | |
| echo "Release tag output is empty; cannot update AUR package." >&2 | |
| exit 1 | |
| fi | |
| version="${TAG_NAME#v}" | |
| aur_repo_dir="$RUNNER_TEMP/aur-tritium-launcher-bin" | |
| installer_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG_NAME}/tritium-linux-x64.tar.gz" | |
| GIT_SSH_COMMAND="ssh -i ~/.ssh/aur -o IdentitiesOnly=yes" \ | |
| git clone ssh://aur@aur.archlinux.org/tritium-launcher-bin.git "$aur_repo_dir" | |
| cp packaging/aur/tritium-launcher-bin/LICENSE "$aur_repo_dir/" | |
| cp packaging/aur/tritium-launcher-bin/tritium-launcher-bin.desktop "$aur_repo_dir/" | |
| cp packaging/aur/tritium-launcher-bin/tritium-launcher.png "$aur_repo_dir/" | |
| cp packaging/aur/tritium-launcher-bin/tritium-launcher.svg "$aur_repo_dir/" | |
| cp packaging/aur/tritium-launcher-bin/PKGBUILD "$aur_repo_dir/" | |
| curl -fsSL -o "$RUNNER_TEMP/tritium-linux-x64.tar.gz" "$installer_url" | |
| tarball_sha="$(sha256sum "$RUNNER_TEMP/tritium-linux-x64.tar.gz" | awk '{print $1}')" | |
| python3 - "$aur_repo_dir/PKGBUILD" "$version" "$tarball_sha" <<'PY' | |
| from pathlib import Path | |
| import re | |
| import sys | |
| pkgbuild = Path(sys.argv[1]) | |
| version = sys.argv[2] | |
| tarball_sha = sys.argv[3] | |
| text = pkgbuild.read_text() | |
| text, version_count = re.subn(r'^pkgver=.*$', f'pkgver={version}', text, count=1, flags=re.MULTILINE) | |
| if version_count != 1: | |
| raise SystemExit('Failed to update pkgver in PKGBUILD') | |
| text, sha_count = re.subn( | |
| r"(sha256sums=\(\s*\n\s*')[^']+(')", | |
| rf"\1{tarball_sha}\2", | |
| text, | |
| count=1, | |
| flags=re.MULTILINE, | |
| ) | |
| if sha_count != 1: | |
| raise SystemExit('Failed to update source tarball sha256 in PKGBUILD') | |
| pkgbuild.write_text(text) | |
| PY | |
| ( | |
| cd "$aur_repo_dir" | |
| makepkg --printsrcinfo > .SRCINFO | |
| if git diff --quiet; then | |
| echo "AUR package is already up to date." | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add PKGBUILD .SRCINFO LICENSE tritium-launcher-bin.desktop tritium-launcher.png tritium-launcher.svg | |
| git commit -m "Update to ${version}" | |
| GIT_SSH_COMMAND="ssh -i ~/.ssh/aur -o IdentitiesOnly=yes" git push origin master | |
| ) | |
| winget: | |
| name: Submit Winget Manifest | |
| runs-on: windows-2025 | |
| needs: release | |
| permissions: | |
| contents: read | |
| if: needs.release.result == 'success' && github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') && !(github.event_name == 'workflow_dispatch' && github.event.inputs.prerelease == 'true') | |
| steps: | |
| - name: Download wingetcreate | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| Invoke-WebRequest "https://aka.ms/wingetcreate/latest" -OutFile "wingetcreate.exe" | |
| - name: Submit Winget update (x64) | |
| shell: pwsh | |
| env: | |
| WINGET_TOKEN: ${{ secrets.WINGET_CREATE_GITHUB_TOKEN }} | |
| WINGET_AUTOMATION_READY: ${{ vars.WINGET_AUTOMATION_READY }} | |
| TAG_NAME: ${{ needs.release.outputs.tag_name }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| if ([string]::IsNullOrWhiteSpace($env:WINGET_TOKEN)) { | |
| Write-Host "WINGET_CREATE_GITHUB_TOKEN is not set. Skipping Winget submission." | |
| exit 0 | |
| } | |
| if ($env:WINGET_AUTOMATION_READY -ne "true") { | |
| Write-Host "WINGET_AUTOMATION_READY is not true. Skipping Winget submission until the first winget-pkgs PR has merged." | |
| exit 0 | |
| } | |
| if ([string]::IsNullOrWhiteSpace($env:TAG_NAME)) { | |
| throw "Release tag output is empty; cannot submit Winget manifest." | |
| } | |
| $version = $env:TAG_NAME.TrimStart('v') | |
| $installerUrl = "https://github.com/${{ github.repository }}/releases/download/$($env:TAG_NAME)/tritium-windows-x64.msi" | |
| $prTitle = "Update ${{ env.WINGET_PACKAGE_IDENTIFIER }} to $version" | |
| $available = $false | |
| for ($attempt = 1; $attempt -le 10; $attempt++) { | |
| try { | |
| Invoke-WebRequest -Uri $installerUrl -Method Head | Out-Null | |
| $available = $true | |
| break | |
| } catch { | |
| Start-Sleep -Seconds 15 | |
| } | |
| } | |
| if (-not $available) { | |
| throw "MSI asset is not reachable at $installerUrl" | |
| } | |
| .\wingetcreate.exe update "${{ env.WINGET_PACKAGE_IDENTIFIER }}" ` | |
| --version "$version" ` | |
| --urls "$installerUrl" ` | |
| --submit ` | |
| --token "$env:WINGET_TOKEN" ` | |
| --prtitle "$prTitle" ` | |
| --no-open |