2
0

release.yml 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. name: Release
  2. # Produces versioned distributables for ALL targets a host can build, into a GitHub Release.
  3. # Each format is built independently so one failing format never blocks the others, and the
  4. # collect step uploads whatever actually got produced (partial success is still published).
  5. # linux (deb/rpm/AppImage) + android (apk) + iso -> ubuntu-latest
  6. # windows (exe) -> windows-latest
  7. # macos (dmg) -> macos-latest
  8. # Skipped/failed formats are logged as ::warning:: and never fail the whole release.
  9. # NOTE: iOS cannot be produced — the project has no iOS Kotlin target (commonMain uses
  10. # JVM-only libs); there is no iOS step.
  11. on:
  12. workflow_dispatch:
  13. push:
  14. branches:
  15. - main
  16. tags:
  17. - 'v*'
  18. # Build jobs only read the repo (least privilege — "read only gh" in the build step);
  19. # only the publish job below requests write to create the release.
  20. permissions:
  21. contents: read
  22. jobs:
  23. package:
  24. name: Package (${{ matrix.target }})
  25. strategy:
  26. fail-fast: false
  27. matrix:
  28. include:
  29. - os: ubuntu-latest
  30. target: linux-android-iso
  31. - os: windows-latest
  32. target: windows
  33. - os: macos-latest
  34. target: macos
  35. runs-on: ${{ matrix.os }}
  36. steps:
  37. - uses: actions/checkout@v4
  38. - name: Set up JDK 17
  39. uses: actions/setup-java@v4
  40. with:
  41. java-version: '17'
  42. distribution: 'temurin'
  43. - name: Setup Gradle
  44. uses: gradle/actions/setup-gradle@v4
  45. - name: Make gradlew executable
  46. shell: bash
  47. run: chmod +x gradlew || true
  48. - name: Read app name + version
  49. id: meta
  50. shell: bash
  51. run: |
  52. echo "version=$(grep -E '^app-pkg-version' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
  53. echo "appname=$(grep -E '^app-name' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
  54. # Decode the Android release keystore from a base64 secret (optional). Without the
  55. # secret the APK is debug-signed by the build fallback — still installable.
  56. - name: Decode Android keystore
  57. if: matrix.os == 'ubuntu-latest'
  58. shell: bash
  59. env:
  60. ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
  61. run: |
  62. if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then
  63. echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore"
  64. echo "ANDROID_KEYSTORE_FILE=$RUNNER_TEMP/release.keystore" >> "$GITHUB_ENV"
  65. echo "Android release keystore decoded"
  66. else
  67. echo "::warning::no ANDROID_KEYSTORE_BASE64 secret — APK will be debug-signed"
  68. fi
  69. # Single apt transaction on ubuntu — avoids dpkg lock races from overlapping apt-get runs.
  70. # Compositor build headers/tools (wayland-scanner, wlroots pkg-config, …) are required for
  71. # :compositor:stageSession -> packageFullDeb -> collectReleases; without them deb/rpm/iso
  72. # never land in releases/ even though the android APK still builds.
  73. - name: Install Linux build dependencies
  74. if: matrix.os == 'ubuntu-latest'
  75. shell: bash
  76. run: |
  77. sudo apt-get update
  78. sudo apt-get install -y \
  79. rpm debootstrap squashfs-tools xorriso mtools dpkg-dev \
  80. grub-common grub-pc-bin grub-efi-amd64-bin debian-archive-keyring zip \
  81. pkg-config libwayland-dev wayland-protocols libwlroots-0.18-dev \
  82. libxkbcommon-dev libcairo2-dev libpixman-1-dev \
  83. || echo "::warning::some apt packages missing — some formats may be skipped"
  84. # ---------- linux + android + iso + reports (ubuntu) ----------
  85. # buildAll = collectReleases (deb/rpm/AppImage/apk -> releases/) + makeIso + reports.
  86. # --continue keeps going when one format fails; makeIso is warn-only so ISO never
  87. # blocks the formats that already succeeded.
  88. - name: Build linux + android + iso + reports
  89. if: matrix.os == 'ubuntu-latest'
  90. shell: bash
  91. continue-on-error: true
  92. env:
  93. ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
  94. ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
  95. ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
  96. run: |
  97. ./gradlew buildAll -PdepCheck=false --continue --stacktrace \
  98. || echo "::warning::buildAll had failures — collecting partial artifacts"
  99. echo "=== releases/ ==="
  100. ls -la releases/ 2>/dev/null || echo "(empty)"
  101. ls -la releases/*.iso 2>/dev/null && echo "ISO produced" \
  102. || echo "::warning::no .iso in releases/ — makeIso skipped or failed"
  103. # ---------- windows (.exe) ----------
  104. - name: Build windows .exe
  105. if: matrix.os == 'windows-latest'
  106. shell: bash
  107. continue-on-error: true
  108. run: ./gradlew :desktopApp:packageReleaseExe -PdepCheck=false --stacktrace || echo "::warning::windows .exe failed — skipped"
  109. # ---------- macos (.dmg) ----------
  110. # Signing/notarization activate only when the Apple Developer ID secrets are present
  111. # (see desktopApp.gradle.kts macOS block); otherwise the .dmg is built unsigned.
  112. - name: Build macOS .dmg
  113. if: matrix.os == 'macos-latest'
  114. shell: bash
  115. continue-on-error: true
  116. env:
  117. MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
  118. MACOS_BUNDLE_ID: ${{ secrets.MACOS_BUNDLE_ID }}
  119. MACOS_NOTARY_APPLE_ID: ${{ secrets.MACOS_NOTARY_APPLE_ID }}
  120. MACOS_NOTARY_PASSWORD: ${{ secrets.MACOS_NOTARY_PASSWORD }}
  121. MACOS_NOTARY_TEAM_ID: ${{ secrets.MACOS_NOTARY_TEAM_ID }}
  122. run: ./gradlew :desktopApp:packageReleaseDmg -PdepCheck=false --stacktrace || echo "::warning::macOS .dmg failed — skipped"
  123. # ---------- collect whatever got produced (versioned names) ----------
  124. # Primary source is releases/ (collectReleases + makeIso). Fall back to jpackage output
  125. # dirs for partial builds and for platform-specific runners (exe/dmg).
  126. - name: Collect artifacts
  127. shell: bash
  128. continue-on-error: true
  129. run: |
  130. set +e
  131. VER='${{ steps.meta.outputs.version }}'
  132. APP='${{ steps.meta.outputs.appname }}'
  133. BASE="$APP-$VER"
  134. PKG=packages/main-release
  135. mkdir -p upload
  136. copy_pkg() {
  137. local dir="$1" ext="$2"
  138. for f in "$dir"/*."$ext"; do
  139. if [ -e "$f" ]; then
  140. cp -v "$f" "upload/$BASE.$ext"
  141. return 0
  142. fi
  143. done
  144. return 0
  145. }
  146. if compgen -G "releases/*" > /dev/null; then
  147. cp -av releases/* upload/
  148. fi
  149. copy_pkg "$PKG/exe" exe
  150. copy_pkg "$PKG/dmg" dmg
  151. [ -f "upload/$BASE.deb" ] || copy_pkg "$PKG/deb" deb
  152. [ -f "upload/$BASE.rpm" ] || copy_pkg "$PKG/rpm" rpm
  153. [ -f "upload/$BASE.AppImage" ] || copy_pkg "$PKG/appimage" AppImage
  154. if [ ! -f "upload/$BASE.apk" ]; then
  155. for f in androidApp/build/outputs/apk/release/*.apk; do
  156. [ -e "$f" ] && cp -v "$f" "upload/$BASE.apk" && break
  157. done
  158. fi
  159. if [ ! -f "upload/$BASE.iso" ]; then
  160. for f in releases/*.iso; do
  161. [ -e "$f" ] && cp -v "$f" "upload/$BASE.iso" && break
  162. done
  163. fi
  164. if [ -d reports ] && [ -n "$(ls -A reports 2>/dev/null)" ]; then
  165. (cd reports && zip -r "../upload/$BASE-reports.zip" .) || echo "::warning::reports zip failed"
  166. fi
  167. echo "=== produced for ${{ matrix.target }} ==="
  168. ls -la upload/ || true
  169. - name: Upload build artifacts
  170. if: always()
  171. uses: actions/upload-artifact@v4
  172. with:
  173. name: dist-${{ matrix.target }}
  174. path: upload/
  175. if-no-files-found: warn
  176. release:
  177. name: Publish GitHub Release
  178. needs: package
  179. if: always()
  180. runs-on: ubuntu-latest
  181. permissions:
  182. contents: write
  183. steps:
  184. - uses: actions/checkout@v4
  185. - name: Read version
  186. id: ver
  187. shell: bash
  188. run: echo "version=$(grep -E '^app-pkg-version' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
  189. - name: Download all artifacts
  190. uses: actions/download-artifact@v4
  191. with:
  192. path: dist
  193. merge-multiple: true
  194. - name: List collected
  195. shell: bash
  196. run: ls -la dist/ || echo "no artifacts produced"
  197. - name: Publish to GitHub Release
  198. uses: softprops/action-gh-release@v2
  199. with:
  200. tag_name: ${{ github.ref_type == 'tag' && github.ref_name || format('v{0}', steps.ver.outputs.version) }}
  201. name: mjdev-desktop v${{ steps.ver.outputs.version }}
  202. files: dist/**
  203. fail_on_unmatched_files: false
  204. env:
  205. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}