2
0

release.yml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. name: Release
  2. # Produces versioned distributables for ALL targets a host can build, into a GitHub Release.
  3. # Each target verifies its own required artifacts. Publishing uses every successful target, so
  4. # apk/exe are not held hostage by Linux-only packaging dependencies.
  5. # linux (deb/rpm/AppImage) + iso -> ubuntu-latest
  6. # android (apk) -> ubuntu-latest
  7. # windows (exe) -> windows-latest
  8. # macos (dmg) -> macos-latest
  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-iso
  31. - os: ubuntu-latest
  32. target: android
  33. - os: windows-latest
  34. target: windows
  35. - os: macos-latest
  36. target: macos
  37. runs-on: ${{ matrix.os }}
  38. steps:
  39. - uses: actions/checkout@v4
  40. - name: Set up JDK 17
  41. uses: actions/setup-java@v4
  42. with:
  43. java-version: '17'
  44. distribution: 'temurin'
  45. - name: Setup Gradle
  46. uses: gradle/actions/setup-gradle@v4
  47. - name: Make gradlew executable
  48. shell: bash
  49. run: chmod +x gradlew || true
  50. - name: Read app name + version
  51. id: meta
  52. shell: bash
  53. run: |
  54. echo "version=$(grep -E '^app-pkg-version' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
  55. echo "appname=$(grep -E '^app-name' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
  56. # Decode the Android release keystore from a base64 secret (optional). Without the
  57. # secret the APK is debug-signed by the build fallback — still installable.
  58. - name: Decode Android keystore
  59. if: matrix.target == 'android'
  60. shell: bash
  61. env:
  62. ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
  63. run: |
  64. if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then
  65. echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore"
  66. echo "ANDROID_KEYSTORE_FILE=$RUNNER_TEMP/release.keystore" >> "$GITHUB_ENV"
  67. echo "Android release keystore decoded"
  68. else
  69. echo "::warning::no ANDROID_KEYSTORE_BASE64 secret — APK will be debug-signed"
  70. fi
  71. # Compositor build headers/tools are required for :compositor:stageSession ->
  72. # packageFullDeb -> collectReleases. Ubuntu 24.04 has wayland-scanner in
  73. # libwayland-bin but does not ship wlroots 0.18, so install that ABI-pinned package
  74. # from the catalog URL instead of letting apt fail the whole dependency transaction.
  75. - name: Install Linux build dependencies
  76. if: matrix.target == 'linux-iso'
  77. shell: bash
  78. run: |
  79. set -euo pipefail
  80. sudo apt-get update
  81. sudo apt-get install -y \
  82. ca-certificates curl rpm debootstrap squashfs-tools xorriso mtools dpkg-dev \
  83. grub-common grub-pc-bin grub-efi-amd64-bin debian-archive-keyring zip \
  84. pkg-config libwayland-dev libwayland-bin wayland-protocols \
  85. libxkbcommon-dev libcairo2-dev libcairo2 libpixman-1-dev libdrm-dev \
  86. libinput-dev libseat-dev libgbm-dev libegl1-mesa-dev libgles2-mesa-dev \
  87. libcap-dev liblcms2-dev libsystemd-dev libudev-dev libvulkan-dev \
  88. libxcb1-dev libxcb-composite0-dev libxcb-dri3-dev libxcb-ewmh-dev \
  89. libxcb-icccm4-dev libxcb-image0-dev libxcb-present-dev libxcb-render0-dev \
  90. libxcb-render-util0-dev libxcb-res0-dev libxcb-shm0-dev libxcb-xfixes0-dev \
  91. libxcb-xinput-dev libx11-xcb-dev
  92. read_catalog_url() {
  93. grep -E "^$1" gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/'
  94. }
  95. mkdir -p "$RUNNER_TEMP/wlroots"
  96. for key in \
  97. ci-ubuntu-libdisplay-info-runtime-deb-url \
  98. ci-ubuntu-libdisplay-info-dev-deb-url \
  99. ci-ubuntu-libliftoff-runtime-deb-url \
  100. ci-ubuntu-libliftoff-dev-deb-url \
  101. ci-ubuntu-libxcb-errors-runtime-deb-url \
  102. ci-ubuntu-libxcb-errors-dev-deb-url \
  103. ci-ubuntu-wlroots-runtime-deb-url \
  104. ci-ubuntu-wlroots-dev-deb-url
  105. do
  106. url="$(read_catalog_url "$key")"
  107. curl -fsSL "$url" -o "$RUNNER_TEMP/wlroots/${key}.deb"
  108. done
  109. sudo apt-get install -y "$RUNNER_TEMP"/wlroots/*.deb
  110. wayland-scanner --version
  111. pkg-config --modversion wlroots-0.18
  112. # ---------- linux + iso + reports (ubuntu) ----------
  113. - name: Build linux + iso + reports
  114. if: matrix.target == 'linux-iso'
  115. shell: bash
  116. run: |
  117. ./gradlew :desktopApp:packageReleaseDistributionForCurrentOS packageAppImageFile packageFullDeb makeIso -PdepCheck=false --stacktrace
  118. echo "=== releases/ ==="
  119. ls -la releases/
  120. # ---------- android (.apk) ----------
  121. - name: Build android .apk
  122. if: matrix.target == 'android'
  123. shell: bash
  124. env:
  125. ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
  126. ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
  127. ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
  128. run: ./gradlew :androidApp:assembleRelease -PdepCheck=false --stacktrace
  129. # ---------- windows (.exe) ----------
  130. - name: Build windows .exe
  131. if: matrix.os == 'windows-latest'
  132. shell: bash
  133. run: ./gradlew :desktopApp:packageReleaseExe -PdepCheck=false --stacktrace
  134. # ---------- macos (.dmg) ----------
  135. # Signing/notarization activate only when the Apple Developer ID secrets are present
  136. # (see desktopApp.gradle.kts macOS block); otherwise the .dmg is built unsigned.
  137. - name: Build macOS .dmg
  138. if: matrix.os == 'macos-latest'
  139. shell: bash
  140. env:
  141. MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
  142. MACOS_BUNDLE_ID: ${{ secrets.MACOS_BUNDLE_ID }}
  143. MACOS_NOTARY_APPLE_ID: ${{ secrets.MACOS_NOTARY_APPLE_ID }}
  144. MACOS_NOTARY_PASSWORD: ${{ secrets.MACOS_NOTARY_PASSWORD }}
  145. MACOS_NOTARY_TEAM_ID: ${{ secrets.MACOS_NOTARY_TEAM_ID }}
  146. run: ./gradlew :desktopApp:packageReleaseDmg -PdepCheck=false --stacktrace
  147. # ---------- collect whatever got produced (versioned names) ----------
  148. # Primary source is releases/ (collectReleases + makeIso). Fall back to jpackage output
  149. # dirs for partial builds and for platform-specific runners (exe/dmg).
  150. - name: Collect artifacts
  151. shell: bash
  152. run: |
  153. set -euo pipefail
  154. VER='${{ steps.meta.outputs.version }}'
  155. APP='${{ steps.meta.outputs.appname }}'
  156. BASE="$APP-$VER"
  157. PKG=packages/main-release
  158. mkdir -p upload
  159. copy_pkg() {
  160. local dir="$1" ext="$2"
  161. for f in "$dir"/*."$ext"; do
  162. if [ -e "$f" ]; then
  163. cp -v "$f" "upload/$BASE.$ext"
  164. return 0
  165. fi
  166. done
  167. return 0
  168. }
  169. if compgen -G "releases/*" > /dev/null; then
  170. cp -av releases/* upload/
  171. fi
  172. copy_pkg "$PKG/exe" exe
  173. copy_pkg "$PKG/dmg" dmg
  174. [ -f "upload/$BASE.deb" ] || copy_pkg "$PKG/deb" deb
  175. [ -f "upload/$BASE.rpm" ] || copy_pkg "$PKG/rpm" rpm
  176. [ -f "upload/$BASE.AppImage" ] || copy_pkg "$PKG/appimage" AppImage
  177. if [ ! -f "upload/$BASE.apk" ]; then
  178. for f in androidApp/build/outputs/apk/release/*.apk; do
  179. [ -e "$f" ] && cp -v "$f" "upload/$BASE.apk" && break
  180. done
  181. fi
  182. if [ ! -f "upload/$BASE.iso" ]; then
  183. for f in releases/*.iso; do
  184. [ -e "$f" ] && cp -v "$f" "upload/$BASE.iso" && break
  185. done
  186. fi
  187. if [ -d reports ] && [ -n "$(ls -A reports 2>/dev/null)" ]; then
  188. (cd reports && zip -r "../upload/$BASE-reports.zip" .)
  189. fi
  190. echo "=== produced for ${{ matrix.target }} ==="
  191. ls -la upload/
  192. require_artifact() {
  193. local file="upload/$1"
  194. if [ ! -s "$file" ]; then
  195. echo "::error::required release artifact is missing or empty: $1"
  196. exit 1
  197. fi
  198. }
  199. case '${{ matrix.target }}' in
  200. linux-iso)
  201. require_artifact "$BASE.deb"
  202. require_artifact "$BASE.rpm"
  203. require_artifact "$BASE.AppImage"
  204. require_artifact "$BASE.iso"
  205. ;;
  206. android)
  207. require_artifact "$BASE.apk"
  208. ;;
  209. windows)
  210. require_artifact "$BASE.exe"
  211. ;;
  212. macos)
  213. require_artifact "$BASE.dmg"
  214. ;;
  215. esac
  216. - name: Upload build artifacts
  217. uses: actions/upload-artifact@v4
  218. with:
  219. name: dist-${{ matrix.target }}
  220. path: upload/
  221. if-no-files-found: warn
  222. release:
  223. name: Publish GitHub Release
  224. needs: package
  225. if: always()
  226. runs-on: ubuntu-latest
  227. permissions:
  228. contents: write
  229. steps:
  230. - uses: actions/checkout@v4
  231. - name: Read version
  232. id: ver
  233. shell: bash
  234. run: echo "version=$(grep -E '^app-pkg-version' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
  235. - name: Download all artifacts
  236. uses: actions/download-artifact@v4
  237. with:
  238. path: dist
  239. merge-multiple: true
  240. - name: List collected
  241. shell: bash
  242. run: ls -la dist/ || echo "no artifacts produced"
  243. - name: Publish to GitHub Release
  244. uses: softprops/action-gh-release@v2
  245. with:
  246. tag_name: ${{ github.ref_type == 'tag' && github.ref_name || format('v{0}', steps.ver.outputs.version) }}
  247. name: mjdev-desktop v${{ steps.ver.outputs.version }}
  248. files: dist/**
  249. fail_on_unmatched_files: false
  250. env:
  251. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}