Release #136
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: 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 |