Skip to content

Release

Release #136

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g., 2.0.0)"
required: true
draft:
description: "Create release as draft"
required: false
default: "true"
prerelease:
description: "Mark release as prerelease"
required: false
default: "false"
permissions:
contents: write
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "21"
- name: Build Linux bundle + JVM jars
run: ./gradlew :lwjgl3:bundleLinuxTar :lwjgl3:jarLinux :core:pluginApiJar -PreleaseNumber="${{ inputs.tag }}"
- uses: actions/upload-artifact@v4
with:
name: octodraw-linux
path: |
dist/*.tar.gz
lwjgl3/build/libs/*.jar
core/build/libs/octodraw-plugin*.jar
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "21"
- name: Build Windows bundle
run: .\\gradlew.bat :lwjgl3:bundleWin -PreleaseNumber="${{ inputs.tag }}"
- name: Build MSI (Windows)
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$version = "${{ inputs.tag }}"
$appName = "Octodraw"
$displayName = "Octodraw 3D"
$publisher = "CN=K3D0"
$packageName = "com.github.alfu32.octodraw"
$jarDir = "lwjgl3\build\libs"
$jarPath = Join-Path $jarDir "octodraw-$version.jar"
$jpackage = Join-Path $env:JAVA_HOME "bin\jpackage.exe"
if (!(Test-Path $jarPath)) { throw "Jar not found: $jarPath" }
& $jpackage --type app-image --name $appName --app-version $version --vendor "alfu32" --win-console `
--input $jarDir --main-jar "octodraw-$version.jar" --dest "dist\jpackage" `
--icon "lwjgl3\icons\logo.ico" `
--java-options "-Xms512m" --java-options "-Xmx6g"
$appImage = "dist\jpackage\$appName"
$pluginsDir = Join-Path $appImage "app\\plugins"
$tutorialsDir = Join-Path $appImage "app\\tutorials"
New-Item $pluginsDir -ItemType Directory -Force | Out-Null
New-Item $tutorialsDir -ItemType Directory -Force | Out-Null
Copy-Item "core\build\libs\octodraw-plugin-api*.jar" $pluginsDir -Force
Copy-Item "scripts\*.groovy" $pluginsDir -Force
Copy-Item "docs\tutorial\*" $tutorialsDir -Recurse -Force
& $jpackage --type msi --name $appName --app-version $version --vendor "alfu32" `
--app-image $appImage --dest "dist\installer" `
--icon "lwjgl3\icons\logo.ico" --win-per-user-install --win-menu --win-shortcut
- name: Prepare MSIX layout
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$version = "${{ inputs.tag }}"
$appName = "Octodraw"
# Must match the reserved Microsoft Store app name exactly.
$displayName = "OctoDraw-Free"
$publisher = "CN=7F0CBC95-8035-4AF2-9B17-C38D58351CE5"
$publisherDisplayName = "K3D0"
# Must match the product identity configured in Partner Center exactly.
$packageName = "K3D0.OctoDraw-Free"
$appImage = "dist\jpackage\$appName"
$msixRoot = "dist\msix"
if (Test-Path $msixRoot) { Remove-Item $msixRoot -Recurse -Force }
New-Item $msixRoot -ItemType Directory -Force | Out-Null
Copy-Item -Path (Join-Path $appImage "*") -Destination $msixRoot -Recurse -Force
$assetsDir = Join-Path $msixRoot "Assets"
New-Item $assetsDir -ItemType Directory -Force | Out-Null
Copy-Item "assets\appicon.png" (Join-Path $assetsDir "Square150x150Logo.png")
Copy-Item "assets\appicon.png" (Join-Path $assetsDir "Square44x44Logo.png")
Copy-Item "assets\appicon.png" (Join-Path $assetsDir "StoreLogo.png")
$baseNames = @("Square150x150Logo", "Square44x44Logo", "StoreLogo")
foreach ($name in $baseNames) {
$scaled = Join-Path $assetsDir ("{0}.scale-100.png" -f $name)
$base = Join-Path $assetsDir ("{0}.png" -f $name)
if ((Test-Path $scaled) -and !(Test-Path $base)) {
Copy-Item $scaled $base -Force
}
}
$manifest = @(
'<?xml version="1.0" encoding="utf-8"?>',
'<Package',
' xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"',
' xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"',
' xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"',
' xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"',
' xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"',
' IgnorableNamespaces="uap uap3 desktop rescap">',
' <Identity Name="__PACKAGE_NAME__" Publisher="__PUBLISHER__" Version="__VERSION__.0" />',
' <Properties>',
' <DisplayName>__DISPLAY_NAME__</DisplayName>',
' <PublisherDisplayName>__PUBLISHER_DISPLAY_NAME__</PublisherDisplayName>',
' <Logo>Source\Assets\StoreLogo.png</Logo>',
' </Properties>',
' <Resources>',
' <Resource Language="en-us" />',
' </Resources>',
' <Dependencies>',
' <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22621.0" />',
' </Dependencies>',
' <Applications>',
' <Application Id="Octodraw" Executable="Source/__APP_NAME__.exe" EntryPoint="Windows.FullTrustApplication">',
' <uap:VisualElements DisplayName="__DISPLAY_NAME__" Description="__DISPLAY_NAME__"',
' Square150x150Logo="Source\Assets\Square150x150Logo.png"',
' Square44x44Logo="Source\Assets\Square44x44Logo.png"',
' BackgroundColor="transparent" />',
' <Extensions>',
' <uap:Extension Category="windows.fileTypeAssociation">',
' <uap:FileTypeAssociation Name="octd">',
' <uap:SupportedFileTypes>',
' <uap:FileType>.octd</uap:FileType>',
' </uap:SupportedFileTypes>',
' </uap:FileTypeAssociation>',
' </uap:Extension>',
' <uap3:Extension Category="windows.appExecutionAlias" Executable="Source\__APP_NAME__.exe" EntryPoint="Windows.FullTrustApplication">',
' <uap3:AppExecutionAlias>',
' <desktop:ExecutionAlias Alias="octodraw.exe" />',
' </uap3:AppExecutionAlias>',
' </uap3:Extension>',
' </Extensions>',
' </Application>',
' </Applications>',
' <Capabilities>',
' <rescap:Capability Name="runFullTrust" />',
' </Capabilities>',
'</Package>'
) -join "`n"
$manifest = $manifest.Replace("__PACKAGE_NAME__", $packageName)
$manifest = $manifest.Replace("__PUBLISHER__", $publisher)
$manifest = $manifest.Replace("__PUBLISHER_DISPLAY_NAME__", $publisherDisplayName)
$manifest = $manifest.Replace("__VERSION__", $version)
$manifest = $manifest.Replace("__DISPLAY_NAME__", $displayName)
$exe = Get-ChildItem $msixRoot -Filter *.exe | Select-Object -First 1
if ($null -eq $exe) { throw "No executable found in $msixRoot" }
$manifest = $manifest.Replace("__APP_NAME__", $exe.BaseName)
$manifestPath = Join-Path $msixRoot "AppxManifest.xml"
$manifest | Out-File -Encoding utf8 $manifestPath
- name: Debug MSIX layout
shell: powershell
run: |
$ErrorActionPreference = "Stop"
Write-Host "MSIX staging root:"
Get-ChildItem -Path dist\\msix -Recurse | ForEach-Object {
if ($_.PSIsContainer) {
Write-Host ("[DIR] " + $_.FullName)
} else {
Write-Host ("[FILE] " + $_.FullName)
}
}
Write-Host "Executables found:"
Get-ChildItem -Path dist\\msix -Recurse -Filter *.exe | ForEach-Object { Write-Host $_.FullName }
- name: Build MSIX (Windows)
id: msix-build
uses: BHznJNs/msix-build@v0.9
with:
app-build-path: dist/msix/.
appx-manifest-path: dist/msix/AppxManifest.xml
icon-path: assets/appsplash.png
priconfig-path: scripts/priconfig.xml
- name: Sign MSIX (Windows)
shell: powershell
env:
K3D_PFX_B64: ${{ secrets.K3D_PFX_B64 }}
K3D_PFX_PASSWORD: ${{ secrets.K3D_PFX_PASSWORD }}
run: |
$ErrorActionPreference = "Stop"
Write-Host "Runner time (UTC):"
Get-Date -Format o
$msixPath = "${{ steps.msix-build.outputs.msix-path }}"
if ([string]::IsNullOrWhiteSpace($msixPath) -or !(Test-Path $msixPath)) {
throw "MSIX not found: $msixPath"
}
if ([string]::IsNullOrWhiteSpace($env:K3D_PFX_B64)) { throw "K3D_PFX_B64 secret is empty" }
if ([string]::IsNullOrWhiteSpace($env:K3D_PFX_PASSWORD)) { throw "K3D_PFX_PASSWORD secret is empty" }
$pfxPath = Join-Path $env:RUNNER_TEMP "k3d-sign.pfx"
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:K3D_PFX_B64))
$signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter signtool.exe |
Sort-Object FullName -Descending | Select-Object -First 1
if ($null -eq $signtool) { throw "signtool.exe not found (Windows SDK required)" }
& $signtool.FullName sign /fd SHA256 /f $pfxPath /p $env:K3D_PFX_PASSWORD /tr http://timestamp.digicert.com /td SHA256 $msixPath
$msiPath = Join-Path "dist\installer" ("octodraw-${{ inputs.tag }}.msi")
if (Test-Path $msiPath) {
& $signtool.FullName sign /fd SHA256 /f $pfxPath /p $env:K3D_PFX_PASSWORD /tr http://timestamp.digicert.com /td SHA256 $msiPath
}
- name: Debug MSIX build workspace
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$workspace = $env:WORKING_DIRECTORY
Write-Host "WORKING_DIRECTORY=$workspace"
if (![string]::IsNullOrWhiteSpace($workspace) -and (Test-Path $workspace)) {
Write-Host "Listing WORKING_DIRECTORY contents:"
Get-ChildItem -Path $workspace -Recurse | ForEach-Object {
if ($_.PSIsContainer) {
Write-Host ("[DIR] " + $_.FullName)
} else {
Write-Host ("[FILE] " + $_.FullName)
}
}
} else {
Write-Host "WORKING_DIRECTORY does not exist"
}
if (Test-Path "D:\\a\\_temp\\msix-build") {
Write-Host "Listing D:\\a\\_temp\\msix-build contents:"
Get-ChildItem -Path "D:\\a\\_temp\\msix-build" -Recurse | ForEach-Object {
if ($_.PSIsContainer) {
Write-Host ("[DIR] " + $_.FullName)
} else {
Write-Host ("[FILE] " + $_.FullName)
}
}
} else {
Write-Host "D:\\a\\_temp\\msix-build does not exist"
}
if (Test-Path "D:\\a\\_temp\\msix-build\\Source") {
Write-Host "Listing D:\\a\\_temp\\msix-build\\Source contents:"
Get-ChildItem -Path "D:\\a\\_temp\\msix-build\\Source" -Recurse | ForEach-Object {
if ($_.PSIsContainer) {
Write-Host ("[DIR] " + $_.FullName)
} else {
Write-Host ("[FILE] " + $_.FullName)
}
}
} else {
Write-Host "D:\\a\\_temp\\msix-build\\Source does not exist"
}
- name: Collect MSIX (Windows)
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$outputPath = "${{ steps.msix-build.outputs.msix-path }}"
if ([string]::IsNullOrWhiteSpace($outputPath)) { throw "MSIX output path missing" }
New-Item "dist\\installer" -ItemType Directory -Force | Out-Null
Copy-Item $outputPath "dist\\installer\\octodraw-${{ inputs.tag }}.msix" -Force
- uses: actions/upload-artifact@v4
with:
name: octodraw-windows
path: |
dist/*.zip
dist/installer/*.msi
dist/installer/*.msix
build-mac:
runs-on: macos-14
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "21"
- name: Build macOS bundle (arm64)
run: ./gradlew :lwjgl3:bundleMacArm64Tar -PreleaseNumber="${{ inputs.tag }}"
- uses: actions/upload-artifact@v4
with:
name: octodraw-mac
path: dist/*.tar.gz
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "21"
- uses: android-actions/setup-android@v3
- name: Install Android SDK components
run: |
sdkmanager "platforms;android-36" "build-tools;36.0.0"
- name: Prepare Android signing keystore
env:
ANDROID_UPLOAD_KEYSTORE_B64: ${{ secrets.ANDROID_UPLOAD_KEYSTORE_B64 }}
ANDROID_UPLOAD_STORE_PASSWORD: ${{ secrets.ANDROID_UPLOAD_STORE_PASSWORD }}
ANDROID_UPLOAD_KEY_ALIAS: ${{ secrets.ANDROID_UPLOAD_KEY_ALIAS }}
ANDROID_UPLOAD_KEY_PASSWORD: ${{ secrets.ANDROID_UPLOAD_KEY_PASSWORD }}
run: |
set -euo pipefail
if [ -z "${ANDROID_UPLOAD_KEYSTORE_B64:-}" ] || [ -z "${ANDROID_UPLOAD_STORE_PASSWORD:-}" ] || [ -z "${ANDROID_UPLOAD_KEY_ALIAS:-}" ] || [ -z "${ANDROID_UPLOAD_KEY_PASSWORD:-}" ]; then
echo "Missing Android signing secrets. Required: ANDROID_UPLOAD_KEYSTORE_B64, ANDROID_UPLOAD_STORE_PASSWORD, ANDROID_UPLOAD_KEY_ALIAS, ANDROID_UPLOAD_KEY_PASSWORD"
exit 1
fi
keystore_path="$RUNNER_TEMP/octodraw-upload.jks"
echo "$ANDROID_UPLOAD_KEYSTORE_B64" | base64 --decode > "$keystore_path"
chmod 600 "$keystore_path"
{
echo "ANDROID_UPLOAD_STORE_FILE=$keystore_path"
echo "ANDROID_UPLOAD_STORE_PASSWORD=$ANDROID_UPLOAD_STORE_PASSWORD"
echo "ANDROID_UPLOAD_KEY_ALIAS=$ANDROID_UPLOAD_KEY_ALIAS"
echo "ANDROID_UPLOAD_KEY_PASSWORD=$ANDROID_UPLOAD_KEY_PASSWORD"
} >> "$GITHUB_ENV"
- name: Build Android APK + AAB bundles
run: |
./gradlew :android:bundleAndroidApk :android:bundleAndroidAab -PreleaseNumber="${{ inputs.tag }}"
- uses: actions/upload-artifact@v4
with:
name: octodraw-android
path: |
dist/octodraw-*.apk
dist/octodraw-*.aab
build-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "21"
- name: Build web bundles
run: ./gradlew :web:bundleWeb :web:bundleWebComponentZip -PreleaseNumber="${{ inputs.tag }}"
- uses: actions/upload-artifact@v4
with:
name: octodraw-web
path: |
dist/octodraw-*-webapp.zip
dist/octodraw-*-webcomponent.zip
release:
runs-on: ubuntu-latest
needs: [build-linux, build-windows, build-mac, build-android, build-web]
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create or update release assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ inputs.tag }}
DRAFT: ${{ inputs.draft }}
PRERELEASE: ${{ inputs.prerelease }}
run: |
set -euo pipefail
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" --draft="$DRAFT" --prerelease="$PRERELEASE"
else
gh release edit "$TAG" --draft="$DRAFT" --prerelease="$PRERELEASE"
fi
files=$(find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" -o -name "*.snap" -o -name "*.jar" -o -name "*.msi" -o -name "*.msix" -o -name "*.apk" -o -name "*.aab" \))
if [ -z "$files" ]; then
echo "No release artifacts found."
exit 1
fi
gh release upload "$TAG" $files --clobber