main.yml 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099
  1. name: Build Kernels
  2. permissions:
  3. contents: write
  4. actions: write
  5. on:
  6. workflow_dispatch:
  7. inputs:
  8. release_type:
  9. description: "Release Type"
  10. type: choice
  11. options:
  12. - Action
  13. - Pre-Release
  14. - Release
  15. default: Action
  16. kernel_build_version:
  17. description: "Kernel Version"
  18. type: choice
  19. options:
  20. - All
  21. - android12-5.10
  22. - android13-5.10
  23. - android13-5.15
  24. - android14-5.15
  25. - android14-6.1
  26. - android15-6.6
  27. - android16-6.12
  28. default: All
  29. brand_name:
  30. description: "Kernel Branding"
  31. type: string
  32. default: Wild
  33. root_flavor:
  34. description: "Root Flavor"
  35. type: choice
  36. options:
  37. - none
  38. - KernelSU-Next
  39. - KernelSU
  40. - ReSukiSU
  41. - All
  42. default: KernelSU-Next
  43. use_susfs:
  44. description: "SUSFS"
  45. type: boolean
  46. default: true
  47. use_nomount:
  48. description: "NoMount"
  49. type: boolean
  50. default: true
  51. use_bbg:
  52. description: "Baseband Guard"
  53. type: boolean
  54. default: true
  55. use_net:
  56. description: "Networking"
  57. type: boolean
  58. default: true
  59. use_ds:
  60. description: "DroidSpaces"
  61. type: boolean
  62. default: true
  63. use_ntsync:
  64. description: "NTSync"
  65. type: boolean
  66. default: true
  67. use_ptrace:
  68. description: "Ptrace Patch"
  69. type: boolean
  70. default: true
  71. use_unicode:
  72. description: "Unicode Fix"
  73. type: boolean
  74. default: true
  75. use_bpf:
  76. description: "BPF Stack (BTF + eBPF + FUSE-BPF)"
  77. type: boolean
  78. default: true
  79. use_perf:
  80. description: "Performance"
  81. type: boolean
  82. default: false
  83. jobs:
  84. prepare-ccache:
  85. name: Prepare ccache binary (download once)
  86. runs-on: ubuntu-latest
  87. steps:
  88. - name: Download custom ccache once
  89. if: false
  90. run: |
  91. set -uo pipefail
  92. echo "::group::Download ccache"
  93. url="https://raw.githubusercontent.com/WildKernels/kernel_patches/refs/heads/main/ccache/ccache-x86-64"
  94. ok=0
  95. for attempt in 1 2 3 4 5 6; do
  96. if curl -LfsS --connect-timeout 30 --max-time 120 -H "User-Agent: Mozilla/5.0" "$url" -o ccache && [ -s ccache ]; then
  97. ok=1; break
  98. fi
  99. echo "attempt $attempt failed (GitHub raw 504/throttle); backing off..."
  100. sleep $(( attempt * 5 + RANDOM % 6 ))
  101. done
  102. if [ "$ok" = 1 ]; then
  103. echo "✅ downloaded ccache once for all matrix jobs"
  104. else
  105. echo "::warning::custom ccache download failed; matrix jobs will fall back to the apt ccache"
  106. rm -f ccache
  107. fi
  108. echo "::endgroup::"
  109. - name: Upload ccache artifact
  110. if: false
  111. uses: actions/upload-artifact@v7
  112. with:
  113. name: ccache-binary
  114. path: ccache
  115. retention-days: 1
  116. if-no-files-found: warn
  117. resolve-sources:
  118. runs-on: ubuntu-latest
  119. outputs:
  120. nomount_commit: ${{ steps.resolve.outputs.nomount_commit }}
  121. kernelsu_commit: ${{ steps.resolve.outputs.kernelsu_commit }}
  122. kernelsu_branch: ${{ steps.resolve.outputs.kernelsu_branch }}
  123. root_flavor: ${{ steps.resolve.outputs.root_flavor }}
  124. root_commit: ${{ steps.resolve.outputs.root_commit }}
  125. root_matrix: ${{ steps.resolve.outputs.root_matrix }}
  126. susfs_commit_android12_5_10: ${{ steps.resolve.outputs.susfs_commit_android12_5_10 }}
  127. susfs_commit_android13_5_10: ${{ steps.resolve.outputs.susfs_commit_android13_5_10 }}
  128. susfs_commit_android13_5_15: ${{ steps.resolve.outputs.susfs_commit_android13_5_15 }}
  129. susfs_commit_android14_5_15: ${{ steps.resolve.outputs.susfs_commit_android14_5_15 }}
  130. susfs_commit_android14_6_1: ${{ steps.resolve.outputs.susfs_commit_android14_6_1 }}
  131. susfs_commit_android15_6_6: ${{ steps.resolve.outputs.susfs_commit_android15_6_6 }}
  132. susfs_commit_android16_6_12: ${{ steps.resolve.outputs.susfs_commit_android16_6_12 }}
  133. kernel_patches_commit: ${{ steps.resolve.outputs.kernel_patches_commit }}
  134. anykernel3_commit: ${{ steps.resolve.outputs.anykernel3_commit }}
  135. droidspaces_commit: ${{ steps.resolve.outputs.droidspaces_commit }}
  136. feature_set: ${{ steps.features.outputs.feature_set }}
  137. steps:
  138. - name: Resolve latest component commits
  139. id: resolve
  140. shell: bash
  141. run: |
  142. set -euo pipefail
  143. retry() {
  144. local n=0
  145. until [ "$n" -ge 5 ]; do
  146. "$@" && return 0
  147. n=$((n+1))
  148. echo "retry $n/5 failed for: $*" >&2
  149. sleep 5
  150. done
  151. return 1
  152. }
  153. resolve_sha() { # repo ref
  154. retry git ls-remote "$1" "$2" | awk '{print $1; exit}'
  155. }
  156. emit() { # key value
  157. if [ -z "$2" ] || ! [[ "$2" =~ ^[0-9a-f]{40}$ ]]; then
  158. echo "Failed to resolve $1 (got: ${2:-empty})" >&2
  159. exit 1
  160. fi
  161. echo "$1=$2" >> "$GITHUB_OUTPUT"
  162. }
  163. # NoMount (dev tip)
  164. emit nomount_commit "$(resolve_sha https://github.com/maxsteeel/nomount.git refs/heads/dev)"
  165. # KernelSU-Next manager — always built from the official repo's dev branch.
  166. # pershoot's fork is used only for the kernel SUSFS integration (root_commit below),
  167. # never for the manager.
  168. emit kernelsu_commit "$(resolve_sha https://github.com/KernelSU-Next/KernelSU-Next.git refs/heads/dev)"
  169. echo "kernelsu_branch=dev" >> "$GITHUB_OUTPUT"
  170. # Selected root implementation(s). Maps the human-facing choice
  171. # (KernelSU-Next / KernelSU / ReSukiSU / All) to internal root-setup
  172. # flavors (next / classic / resukisu). 'none' leaves them empty so
  173. # build.yml falls back to its legacy KernelSU-Next path. 'All' emits a
  174. # matrix of all three root flavors plus each pinned commit.
  175. #
  176. # Always resolve all three branch-tip commits so 'All' can fan out.
  177. CLASSIC_COMMIT="$(resolve_sha https://github.com/tiann/KernelSU.git refs/heads/main)"
  178. RESUKISU_COMMIT="$(resolve_sha https://github.com/ReSukiSU/ReSukiSU.git refs/heads/main)"
  179. if [ "${{ inputs.use_susfs }}" = "true" ]; then
  180. NEXT_COMMIT="$(resolve_sha https://github.com/pershoot/KernelSU-Next.git refs/heads/dev-susfs)"
  181. else
  182. NEXT_COMMIT="$(resolve_sha https://github.com/KernelSU-Next/KernelSU-Next.git refs/heads/dev)"
  183. fi
  184. mk_entry() { # flavor variant commit
  185. printf '{"root_flavor":"%s","variant":"%s","root_commit":"%s"}' "$1" "$2" "$3"
  186. }
  187. case "${{ inputs.root_flavor }}" in
  188. KernelSU)
  189. emit root_flavor "classic"
  190. emit root_commit "$CLASSIC_COMMIT"
  191. ROOT_MATRIX="[$(mk_entry classic KernelSU "$CLASSIC_COMMIT")]"
  192. ;;
  193. KernelSU-Next)
  194. emit root_flavor "next"
  195. emit root_commit "$NEXT_COMMIT"
  196. ROOT_MATRIX="[$(mk_entry next KernelSU-Next "$NEXT_COMMIT")]"
  197. ;;
  198. ReSukiSU)
  199. emit root_flavor "resukisu"
  200. emit root_commit "$RESUKISU_COMMIT"
  201. ROOT_MATRIX="[$(mk_entry resukisu ReSukiSU "$RESUKISU_COMMIT")]"
  202. ;;
  203. All)
  204. # Fan out to all three root variants, each with its pinned commit.
  205. ROOT_MATRIX="[$(mk_entry next KernelSU-Next "$NEXT_COMMIT"),$(mk_entry classic KernelSU "$CLASSIC_COMMIT"),$(mk_entry resukisu ReSukiSU "$RESUKISU_COMMIT")]"
  206. echo "root_flavor=" >> "$GITHUB_OUTPUT"
  207. echo "root_commit=" >> "$GITHUB_OUTPUT"
  208. ;;
  209. *)
  210. # none: legacy path, no explicit root variant
  211. echo "root_flavor=" >> "$GITHUB_OUTPUT"
  212. echo "root_commit=" >> "$GITHUB_OUTPUT"
  213. ROOT_MATRIX="[{\"root_flavor\":\"\",\"variant\":\"\",\"root_commit\":\"\"}]"
  214. ;;
  215. esac
  216. echo "root_matrix=$ROOT_MATRIX" >> "$GITHUB_OUTPUT"
  217. # SUSFS per supported Android/kernel branch (gitlab simonpunk/susfs4ksu)
  218. declare -A SUSFS_BRANCHES=(
  219. [susfs_commit_android12_5_10]="gki-android12-5.10"
  220. [susfs_commit_android13_5_10]="gki-android13-5.10"
  221. [susfs_commit_android13_5_15]="gki-android13-5.15"
  222. [susfs_commit_android14_5_15]="gki-android14-5.15"
  223. [susfs_commit_android14_6_1]="gki-android14-6.1"
  224. [susfs_commit_android15_6_6]="gki-android15-6.6"
  225. [susfs_commit_android16_6_12]="gki-android16-6.12"
  226. )
  227. for key in "${!SUSFS_BRANCHES[@]}"; do
  228. emit "$key" "$(resolve_sha https://gitlab.com/simonpunk/susfs4ksu.git refs/heads/${SUSFS_BRANCHES[$key]})"
  229. done
  230. # Tracked build helpers (resolved automatically, before the run)
  231. emit kernel_patches_commit "$(resolve_sha https://github.com/WildKernels/kernel_patches.git refs/heads/main)"
  232. emit anykernel3_commit "$(resolve_sha https://github.com/WildKernels/AnyKernel3.git refs/heads/gki-2.0)"
  233. emit droidspaces_commit "$(resolve_sha https://github.com/ravindu644/Droidspaces-OSS.git refs/heads/main)"
  234. echo "Resolved:"
  235. env | grep -E '^(nomount_commit|kernelsu_commit|kernelsu_branch|root_commit|susfs_commit_|kernel_patches_commit|anykernel3_commit|droidspaces_commit)' | sort
  236. - name: Resolve manager builds for selected root flavor(s)
  237. id: manager
  238. shell: bash
  239. run: |
  240. set -euo pipefail
  241. # Every run must ship the EXACT manager build(s) matching the selected
  242. # root's stock/sync commit, downloaded as artifacts. If any exact run
  243. # can't be found, fail NOW (before any kernel builds). For 'All' this
  244. # enumerates all three flavors; otherwise just the one selected.
  245. resolve_one() { # flavor
  246. local flavor="$1"
  247. local owner repo wf stock
  248. case "$flavor" in
  249. next)
  250. owner="KernelSU-Next"; repo="KernelSU-Next"; wf="build-manager-ci.yml"
  251. git clone --depth=1 --branch dev-susfs "https://github.com/pershoot/KernelSU-Next.git" /tmp/ksu-manager-check
  252. cd /tmp/ksu-manager-check
  253. git remote add official "https://github.com/KernelSU-Next/KernelSU-Next.git" || true
  254. git fetch --depth=1 official dev
  255. stock=$(git merge-base HEAD FETCH_HEAD)
  256. ;;
  257. classic)
  258. owner="tiann"; repo="KernelSU"; wf="build-manager.yml"
  259. stock=$(git ls-remote https://github.com/tiann/KernelSU.git refs/heads/main | awk '{print $1; exit}')
  260. ;;
  261. resukisu)
  262. owner="ReSukiSU"; repo="ReSukiSU"; wf="build-manager.yml"
  263. stock=$(git ls-remote https://github.com/ReSukiSU/ReSukiSU.git refs/heads/main | awk '{print $1; exit}')
  264. ;;
  265. *)
  266. echo "skipping unknown flavor: $flavor" >&2
  267. return 0
  268. ;;
  269. esac
  270. run_id=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  271. "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?head_sha=${stock}" \
  272. | jq -r '.workflow_runs[0].id // empty')
  273. if [[ -z "${run_id}" ]]; then
  274. echo "ERROR: no ${repo} manager build exists for stock commit ${stock}" >&2
  275. exit 1
  276. fi
  277. printf '{"flavor":"%s","owner":"%s","repo":"%s","wf":"%s","stock":"%s","run_id":%s}' \
  278. "$flavor" "$owner" "$repo" "$wf" "$stock" "$run_id"
  279. }
  280. FLAVORS="${{ inputs.root_flavor }}"
  281. echo "Root flavor: ${FLAVORS}"
  282. LIST=""
  283. case "${FLAVORS}" in
  284. All) LIST="$(resolve_one next),$(resolve_one classic),$(resolve_one resukisu)" ;;
  285. KernelSU-Next) LIST="$(resolve_one next)" ;;
  286. KernelSU) LIST="$(resolve_one classic)" ;;
  287. ReSukiSU) LIST="$(resolve_one resukisu)" ;;
  288. *) echo "Skipping manager resolution (no root flavor / none)." ;;
  289. esac
  290. if [ -n "$LIST" ]; then
  291. echo "manager_list=[$LIST]" >> "$GITHUB_OUTPUT"
  292. echo "Exact manager builds resolved."
  293. else
  294. echo "manager_list=" >> "$GITHUB_OUTPUT"
  295. fi
  296. - name: Download manager APK artifacts
  297. if: ${{ steps.manager.outputs.manager_list != '' }}
  298. shell: bash
  299. run: |
  300. set -euo pipefail
  301. mkdir -p release-assets/manager
  302. echo '${{ steps.manager.outputs.manager_list }}' \
  303. | jq -c '.[]' | while read -r M; do
  304. OWNER=$(echo "$M" | jq -r .owner)
  305. REPO=$(echo "$M" | jq -r .repo)
  306. RUN_ID=$(echo "$M" | jq -r .run_id)
  307. echo "Downloading $REPO manager from run $RUN_ID"
  308. mkdir -p "release-assets/manager/$REPO"
  309. cd "release-assets/manager/$REPO"
  310. ARTIFACTS_JSON=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  311. "https://api.github.com/repos/${OWNER}/${REPO}/actions/runs/${RUN_ID}/artifacts")
  312. echo "$ARTIFACTS_JSON" | jq -r '.artifacts[].id' | while read -r AID; do
  313. curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  314. -o "manager-${AID}.zip" \
  315. "https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${AID}/zip"
  316. unzip -o "manager-${AID}.zip" >/dev/null 2>&1 || true
  317. rm -f "manager-${AID}.zip"
  318. done
  319. cd "$(git rev-parse --show-toplevel 2>/dev/null || echo /github/workspace)"
  320. find "release-assets/manager/$REPO" -type f \( -iname '*.apk' -o -iname '*.zip' \) -print
  321. done
  322. ls -la release-assets/manager
  323. - name: Upload manager APK as artifact
  324. if: ${{ steps.manager.outputs.manager_list != '' }}
  325. uses: actions/upload-artifact@v4
  326. with:
  327. name: manager-apk
  328. path: release-assets/manager/
  329. if-no-files-found: error
  330. - name: Compose requested feature set
  331. id: features
  332. shell: bash
  333. run: |
  334. set -euo pipefail
  335. # SUSFS needs a root implementation (KernelSU / KernelSU-Next / ReSukiSU)
  336. # to hook into. Without one the kernel wouldn't actually exercise the
  337. # SUSFS root-hiding patches, so fail fast instead of building a useless kernel.
  338. if [ "${{ inputs.use_susfs }}" = "true" ] && [ "${{ inputs.root_flavor }}" = "none" ]; then
  339. echo "ERROR: SUSFS requires a root implementation, but root_flavor is 'none'." >&2
  340. echo "Pick KernelSU-Next, KernelSU, or ReSukiSU in the Root Implementation box" >&2
  341. echo "(SUSFS patches SUSFS's root-hiding on top of the root solution)." >&2
  342. exit 1
  343. fi
  344. # A Release/Pre-Release ships whichever manger build(s) were resolved
  345. # in the manager step above (exact match per root flavor). No root
  346. # restriction applies here anymore -- KernelSU-Next, KernelSU, ReSukiSU,
  347. # or 'All' are all valid release targets.
  348. FEATURES=""
  349. [ "${{ inputs.use_susfs }}" = "true" ] && FEATURES="${FEATURES}SUSFS+"
  350. [ "${{ inputs.use_nomount }}" = "true" ] && FEATURES="${FEATURES}NoMount+"
  351. [ "${{ inputs.use_bbg }}" = "true" ] && FEATURES="${FEATURES}BBG+"
  352. [ "${{ inputs.use_net }}" = "true" ] && FEATURES="${FEATURES}NET+"
  353. [ "${{ inputs.use_ds }}" = "true" ] && FEATURES="${FEATURES}DS+"
  354. [ "${{ inputs.use_ntsync }}" = "true" ] && FEATURES="${FEATURES}NTSync+"
  355. [ "${{ inputs.use_ptrace }}" = "true" ] && FEATURES="${FEATURES}Ptrace+"
  356. [ "${{ inputs.use_unicode }}" = "true" ] && FEATURES="${FEATURES}Unicode+"
  357. [ "${{ inputs.use_bpf }}" = "true" ] && FEATURES="${FEATURES}BPF+"
  358. [ "${{ inputs.use_perf }}" = "true" ] && FEATURES="${FEATURES}Perf+"
  359. echo "feature_set=${FEATURES%+}" >> "$GITHUB_OUTPUT"
  360. - name: Clone shared source repos once
  361. if: ${{ inputs.use_nomount }}
  362. shell: bash
  363. run: |
  364. set -euo pipefail
  365. mkdir -p sources
  366. # NoMount kernel sources at the resolved dev tip
  367. git clone --depth=1 --branch dev https://github.com/maxsteeel/nomount.git sources/nomount
  368. [ "$(git -C sources/nomount rev-parse HEAD)" = "${{ steps.resolve.outputs.nomount_commit }}" ] || \
  369. { echo "NoMount clone HEAD mismatch" >&2; exit 1; }
  370. echo "sources_dir=$PWD/sources" >> "$GITHUB_ENV"
  371. - name: Upload sources artifact
  372. if: ${{ inputs.use_nomount }}
  373. uses: actions/upload-artifact@v7
  374. with:
  375. name: sources
  376. path: sources
  377. retention-days: 1
  378. if-no-files-found: error
  379. compression-level: 9
  380. build-nomount-module:
  381. needs: resolve-sources
  382. runs-on: ubuntu-latest
  383. steps:
  384. - uses: actions/checkout@v7
  385. if: ${{ inputs.use_nomount }}
  386. - name: Build NoMount metamodule
  387. id: nomount-metamodule
  388. if: ${{ inputs.use_nomount }}
  389. uses: ./.github/actions/nomount-metamodule
  390. with:
  391. commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  392. - name: Upload NoMount metamodule
  393. if: ${{ inputs.use_nomount }}
  394. uses: actions/upload-artifact@v7
  395. with:
  396. name: NoMount-Metamodule
  397. path: ${{ steps.nomount-metamodule.outputs.module_path }}
  398. if-no-files-found: error
  399. compression-level: 0
  400. build-fuse-bpf:
  401. needs: resolve-sources
  402. runs-on: ubuntu-latest
  403. steps:
  404. - uses: actions/checkout@v7
  405. if: ${{ inputs.use_bpf }}
  406. - name: Sparse-checkout FUSE-BPF source from kernel/common (android16-6.12-lts)
  407. if: ${{ inputs.use_bpf }}
  408. shell: bash
  409. run: |
  410. set -euo pipefail
  411. # Only the daemon source + the headers it compiles against are needed,
  412. # NOT the full ~16GB kernel tree (which includes ~12GB of prebuilts).
  413. git clone --depth 1 --filter=blob:none --sparse \
  414. --branch android16-6.12-lts \
  415. https://android.googlesource.com/kernel/common \
  416. "${{ github.workspace }}/kernel/common"
  417. cd "${{ github.workspace }}/kernel/common"
  418. git sparse-checkout set \
  419. tools/testing/selftests/filesystems/fuse \
  420. include \
  421. arch/arm64/include \
  422. tools/testing/selftests
  423. - name: Build FUSE-BPF Userspace Daemon (Issue #211)
  424. if: ${{ inputs.use_bpf }}
  425. uses: ./.github/actions/fuse-bpf
  426. with:
  427. version: android16-6.12
  428. kernel_version: 6.12
  429. android_version: android16
  430. build-android12-5-10:
  431. needs:
  432. - prepare-ccache
  433. - resolve-sources
  434. if: ${{ !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android12-5.10') }}
  435. strategy:
  436. fail-fast: false
  437. matrix:
  438. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  439. uses: ./.github/workflows/prepare.yml
  440. with:
  441. config_file: .github/config/android12-5.10.json
  442. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  443. susfs_commit: ${{ needs.resolve-sources.outputs.susfs_commit_android12_5_10 }}
  444. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  445. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  446. root_flavor: ${{ matrix.root.root_flavor }}
  447. root_commit: ${{ matrix.root.root_commit }}
  448. variant: ${{ matrix.root.variant }}
  449. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  450. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  451. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  452. brand_name: ${{ inputs.brand_name }}
  453. secrets: inherit
  454. build-android13-5-10:
  455. needs:
  456. - prepare-ccache
  457. - resolve-sources
  458. if: ${{ !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android13-5.10') }}
  459. strategy:
  460. fail-fast: false
  461. matrix:
  462. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  463. uses: ./.github/workflows/prepare.yml
  464. with:
  465. config_file: .github/config/android13-5.10.json
  466. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  467. susfs_commit: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_10 }}
  468. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  469. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  470. root_flavor: ${{ matrix.root.root_flavor }}
  471. root_commit: ${{ matrix.root.root_commit }}
  472. variant: ${{ matrix.root.variant }}
  473. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  474. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  475. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  476. brand_name: ${{ inputs.brand_name }}
  477. secrets: inherit
  478. build-android13-5-15:
  479. needs:
  480. - prepare-ccache
  481. - resolve-sources
  482. if: ${{ !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android13-5.15') }}
  483. strategy:
  484. fail-fast: false
  485. matrix:
  486. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  487. uses: ./.github/workflows/prepare.yml
  488. with:
  489. config_file: .github/config/android13-5.15.json
  490. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  491. susfs_commit: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_15 }}
  492. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  493. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  494. root_flavor: ${{ matrix.root.root_flavor }}
  495. root_commit: ${{ matrix.root.root_commit }}
  496. variant: ${{ matrix.root.variant }}
  497. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  498. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  499. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  500. brand_name: ${{ inputs.brand_name }}
  501. secrets: inherit
  502. build-android14-5-15:
  503. needs:
  504. - prepare-ccache
  505. - resolve-sources
  506. if: ${{ !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android14-5.15') }}
  507. strategy:
  508. fail-fast: false
  509. matrix:
  510. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  511. uses: ./.github/workflows/prepare.yml
  512. with:
  513. config_file: .github/config/android14-5.15.json
  514. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  515. susfs_commit: ${{ needs.resolve-sources.outputs.susfs_commit_android14_5_15 }}
  516. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  517. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  518. root_flavor: ${{ matrix.root.root_flavor }}
  519. root_commit: ${{ matrix.root.root_commit }}
  520. variant: ${{ matrix.root.variant }}
  521. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  522. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  523. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  524. brand_name: ${{ inputs.brand_name }}
  525. secrets: inherit
  526. build-android14-6-1:
  527. needs:
  528. - prepare-ccache
  529. - resolve-sources
  530. if: ${{ !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android14-6.1') }}
  531. strategy:
  532. fail-fast: false
  533. matrix:
  534. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  535. uses: ./.github/workflows/prepare.yml
  536. with:
  537. config_file: .github/config/android14-6.1.json
  538. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  539. susfs_commit: ${{ needs.resolve-sources.outputs.susfs_commit_android14_6_1 }}
  540. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  541. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  542. root_flavor: ${{ matrix.root.root_flavor }}
  543. root_commit: ${{ matrix.root.root_commit }}
  544. variant: ${{ matrix.root.variant }}
  545. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  546. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  547. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  548. brand_name: ${{ inputs.brand_name }}
  549. secrets: inherit
  550. build-android15-6-6:
  551. needs:
  552. - prepare-ccache
  553. - resolve-sources
  554. if: ${{ !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android15-6.6') }}
  555. strategy:
  556. fail-fast: false
  557. matrix:
  558. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  559. uses: ./.github/workflows/prepare.yml
  560. with:
  561. config_file: .github/config/android15-6.6.json
  562. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  563. susfs_commit: ${{ needs.resolve-sources.outputs.susfs_commit_android15_6_6 }}
  564. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  565. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  566. root_flavor: ${{ matrix.root.root_flavor }}
  567. root_commit: ${{ matrix.root.root_commit }}
  568. variant: ${{ matrix.root.variant }}
  569. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  570. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  571. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  572. brand_name: ${{ inputs.brand_name }}
  573. secrets: inherit
  574. build-android16-6-12:
  575. needs:
  576. - prepare-ccache
  577. - resolve-sources
  578. if: ${{ !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android16-6.12') }}
  579. strategy:
  580. fail-fast: false
  581. matrix:
  582. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  583. uses: ./.github/workflows/prepare.yml
  584. with:
  585. config_file: .github/config/android16-6.12.json
  586. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  587. susfs_commit: ${{ needs.resolve-sources.outputs.susfs_commit_android16_6_12 }}
  588. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  589. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  590. root_flavor: ${{ matrix.root.root_flavor }}
  591. root_commit: ${{ matrix.root.root_commit }}
  592. variant: ${{ matrix.root.variant }}
  593. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  594. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  595. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  596. brand_name: ${{ inputs.brand_name }}
  597. secrets: inherit
  598. rej:
  599. if: ${{ !inputs.test_release_notes && always() }}
  600. runs-on: ubuntu-latest
  601. permissions:
  602. actions: read
  603. contents: read
  604. needs:
  605. - build-android12-5-10
  606. - build-android13-5-10
  607. - build-android13-5-15
  608. - build-android14-5-15
  609. - build-android14-6-1
  610. - build-android15-6-6
  611. - build-android16-6-12
  612. steps:
  613. - name: Download Misc Artifacts
  614. uses: actions/download-artifact@v7
  615. with:
  616. path: ./downloaded-artifacts
  617. pattern: '*-Rejects'
  618. merge-multiple: false
  619. - name: Process Reject Artifacts
  620. run: |
  621. mkdir -p aio-rejects
  622. # Iterate over Rejects artifacts in downloaded-artifacts
  623. for dir in ./downloaded-artifacts/*-Rejects; do
  624. [ -d "$dir" ] || continue
  625. dirname=$(basename "$dir")
  626. original_name=${dirname%-Rejects}
  627. source_dir="$dir"
  628. if [ -d "$dir/patch-rejects" ]; then
  629. source_dir="$dir/patch-rejects"
  630. fi
  631. mapfile -t rej_files < <(find "$source_dir" -type f -name '*.rej' | sort)
  632. filtered_rej_files=()
  633. for rej_file in "${rej_files[@]}"; do
  634. rel_rej="${rej_file#${source_dir}/}"
  635. rel_name="$(basename "$rel_rej")"
  636. if [ "$rel_rej" = "common/mm/rmap.c.rej" ] || [ "$rel_name" = "i2c-nomadik.c.rej" ]; then
  637. echo "Skipping $rel_rej because it is an ignored reject file"
  638. continue
  639. fi
  640. filtered_rej_files+=("$rej_file")
  641. done
  642. if [ "${#filtered_rej_files[@]}" -eq 0 ]; then
  643. echo "Skipping $dirname because it only contains ignored reject files"
  644. continue
  645. fi
  646. mkdir -p "aio-rejects/$original_name"
  647. if [ -d "$dir/patch-rejects" ]; then
  648. cp -r "$dir/patch-rejects/." "aio-rejects/$original_name/"
  649. else
  650. cp -r "$dir/." "aio-rejects/$original_name/"
  651. fi
  652. find "aio-rejects/$original_name" -type f -name 'i2c-nomadik.c.rej' -delete
  653. done
  654. if [ "$(ls -A aio-rejects)" ]; then
  655. cd aio-rejects
  656. zip -r -q -9 ../AIO-REJ.zip .
  657. cd ..
  658. else
  659. echo "No rejects found to upload."
  660. fi
  661. - name: Rejects Summary
  662. run: |
  663. echo "Reject artifacts were collected locally only and are not uploaded." >> "$GITHUB_STEP_SUMMARY"
  664. - name: Upload AIO-REJ Artifact
  665. uses: actions/upload-artifact@v7
  666. with:
  667. name: AIO-REJ
  668. path: AIO-REJ.zip
  669. if-no-files-found: ignore
  670. summary:
  671. if: ${{ !inputs.test_release_notes && always() }}
  672. runs-on: ubuntu-latest
  673. permissions:
  674. actions: write
  675. contents: read
  676. needs:
  677. - resolve-sources
  678. - build-android12-5-10
  679. - build-android13-5-10
  680. - build-android13-5-15
  681. - build-android14-5-15
  682. - build-android14-6-1
  683. - build-android15-6-6
  684. - build-android16-6-12
  685. steps:
  686. - name: Download All Build Summaries
  687. uses: actions/download-artifact@v7
  688. with:
  689. path: ./summaries
  690. pattern: '*-Summary'
  691. merge-multiple: true
  692. - name: 📋 Workflow Build Summary
  693. run: |
  694. {
  695. echo "# 📋 Build Summary — $(date -u '+%Y-%m-%d %H:%M UTC')"
  696. echo
  697. echo "**Feature Set:** ${{ needs.resolve-sources.outputs.feature_set }}"
  698. echo "**Root Flavor:** ${{ inputs.root_flavor || 'none' }}"
  699. echo "**Brand:** ${{ inputs.brand_name || 'Wild' }}"
  700. echo
  701. # Count successes and failures
  702. SUCCESS_COUNT=0
  703. FAIL_COUNT=0
  704. # Sort summaries so they appear in a consistent order
  705. for f in ./summaries/*.md; do
  706. [ -f "$f" ] || continue
  707. if grep -q "✅ Success" "$f"; then
  708. SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
  709. else
  710. FAIL_COUNT=$((FAIL_COUNT + 1))
  711. fi
  712. done
  713. TOTAL=$((SUCCESS_COUNT + FAIL_COUNT))
  714. echo "**Results:** ${SUCCESS_COUNT}/${TOTAL} succeeded"
  715. if [ "$FAIL_COUNT" -gt 0 ]; then
  716. echo " - ❌ ${FAIL_COUNT} failed"
  717. fi
  718. echo
  719. echo "---"
  720. echo
  721. # Output each summary
  722. for f in ./summaries/*.md; do
  723. [ -f "$f" ] || continue
  724. cat "$f"
  725. echo
  726. echo "---"
  727. echo
  728. done
  729. echo
  730. echo "> **SUSFS Commits:**"
  731. echo "> - android12-5.10: ${{ needs.resolve-sources.outputs.susfs_commit_android12_5_10 }}"
  732. echo "> - android13-5.10: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_10 }}"
  733. echo "> - android13-5.15: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_15 }}"
  734. echo "> - android14-5.15: ${{ needs.resolve-sources.outputs.susfs_commit_android14_5_15 }}"
  735. echo "> - android14-6.1: ${{ needs.resolve-sources.outputs.susfs_commit_android14_6_1 }}"
  736. echo "> - android15-6.6: ${{ needs.resolve-sources.outputs.susfs_commit_android15_6_6 }}"
  737. echo "> - android16-6.12: ${{ needs.resolve-sources.outputs.susfs_commit_android16_6_12 }}"
  738. } >> "$GITHUB_STEP_SUMMARY"
  739. - name: 🧹 Cleanup Summary Files
  740. if: ${{ always() }}
  741. run: rm -rf ./summaries
  742. - name: 🧹 Delete Summary Artifacts
  743. if: ${{ always() }}
  744. env:
  745. GH_TOKEN: ${{ github.token }}
  746. run: |
  747. # Delete all *-Summary artifacts from this run since they're now consolidated
  748. RUN_ID="${{ github.run_id }}"
  749. ARTIFACTS=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name | endswith("-Summary")) | .id')
  750. for id in $ARTIFACTS; do
  751. echo "Deleting summary artifact id=$id"
  752. gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/${id}" || true
  753. done
  754. release:
  755. runs-on: ubuntu-latest
  756. if: ${{ github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.release_type == 'Pre-Release' || inputs.release_type == 'Release') }}
  757. permissions:
  758. contents: write
  759. needs:
  760. - build-android12-5-10
  761. - build-android13-5-10
  762. - build-android13-5-15
  763. - build-android14-5-15
  764. - build-android14-6-1
  765. - build-android15-6-6
  766. - build-android16-6-12
  767. - build-nomount-module
  768. env:
  769. GH_TOKEN: ${{ github.token }}
  770. RELEASE_NOTES: .github/config/RELEASE_NOTES.md
  771. outputs:
  772. new_tag: ${{ steps.tag.outputs.new_tag }}
  773. steps:
  774. - name: Validate Selected Builds
  775. run: |
  776. set -euo pipefail
  777. failed=0
  778. current_type="${{ inputs.kernel_build_version }}"
  779. check_selected() {
  780. local target="$1"
  781. local job_name="$2"
  782. local result="$3"
  783. if [[ "$current_type" == "all" || "$current_type" == "$target" ]]; then
  784. if [[ "$result" != "success" ]]; then
  785. echo "Required job $job_name did not succeed (result: $result)"
  786. failed=1
  787. fi
  788. fi
  789. }
  790. check_selected "android12-5.10" "build-android12-5-10" "${{ needs.build-android12-5-10.result }}"
  791. check_selected "android13-5.10" "build-android13-5-10" "${{ needs.build-android13-5-10.result }}"
  792. check_selected "android13-5.15" "build-android13-5-15" "${{ needs.build-android13-5-15.result }}"
  793. check_selected "android14-5.15" "build-android14-5-15" "${{ needs.build-android14-5-15.result }}"
  794. check_selected "android14-6.1" "build-android14-6-1" "${{ needs.build-android14-6-1.result }}"
  795. check_selected "android15-6.6" "build-android15-6-6" "${{ needs.build-android15-6-6.result }}"
  796. check_selected "android16-6.12" "build-android16-6-12" "${{ needs.build-android16-6-12.result }}"
  797. if [[ "$failed" -ne 0 ]]; then
  798. exit 1
  799. fi
  800. - name: Free Disk Space
  801. if: true
  802. uses: endersonmenezes/free-disk-space@v3 # Use @main for latest, @v3 for stable
  803. with:
  804. remove_android: true
  805. remove_dotnet: true
  806. remove_haskell: true
  807. remove_tool_cache: true
  808. remove_swap: true
  809. remove_packages: "azure-cli google-cloud-cli microsoft-edge-stable google-chrome-stable firefox postgresql* temurin-* *llvm* mysql* dotnet-sdk-*"
  810. remove_packages_one_command: true
  811. remove_folders: "/usr/share/swift /usr/share/miniconda /usr/share/az* /usr/local/lib/node_modules /usr/local/share/chromium /usr/local/share/powershell /usr/local/julia /usr/local/aws-cli /usr/local/aws-sam-cli /usr/share/gradle"
  812. rm_cmd: "rmz" # Use 'rmz' for faster deletion (default: 'rm')
  813. rmz_version: "3.1.1" # Required when rm_cmd is 'rmz'
  814. testing: false
  815. - name: Checkout code
  816. uses: actions/checkout@v7
  817. - name: Generate New Tag
  818. id: tag
  819. if: inputs.release_type != 'Action'
  820. run: |
  821. mapfile -t RELEASE_TAGS < <(gh api --paginate "repos/${{ github.repository }}/tags?per_page=100" --jq '.[].name' 2>/dev/null | grep -E '^r[0-9]+$' || true)
  822. LATEST_REV=0
  823. for TAG in "${RELEASE_TAGS[@]}"; do
  824. REV="${TAG#r}"
  825. if [[ "$REV" =~ ^[0-9]+$ && "$REV" -gt "$LATEST_REV" ]]; then
  826. LATEST_REV="$REV"
  827. fi
  828. done
  829. if [[ "$LATEST_REV" -gt 0 ]]; then
  830. NEW_TAG="r$((LATEST_REV + 1))"
  831. else
  832. NEW_TAG="r1"
  833. fi
  834. if [[ ${#RELEASE_TAGS[@]} -gt 0 ]]; then
  835. echo "Latest r-tag: r${LATEST_REV}"
  836. else
  837. echo "No existing r-tags found; starting at r1"
  838. fi
  839. echo "New tag: $NEW_TAG"
  840. echo "NEW_TAG=${NEW_TAG}" >> $GITHUB_ENV
  841. echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
  842. - name: Set Release Title
  843. id: release_meta
  844. run: |
  845. set -euo pipefail
  846. # Format release title: convert rN -> Release N
  847. if [[ "${NEW_TAG}" =~ ^r([0-9]+)$ ]]; then
  848. REV_NUM="${BASH_REMATCH[1]}"
  849. release_title="Release ${REV_NUM}"
  850. else
  851. release_title="Release ${NEW_TAG}"
  852. fi
  853. echo "release_title=$release_title" >> "$GITHUB_OUTPUT"
  854. - name: Extract KernelSU Version Info
  855. id: ksu_version
  856. run: |
  857. set -euo pipefail
  858. KSU_REPO="https://github.com/pershoot/KernelSU-Next.git"
  859. KSU_INPUT="dev-susfs"
  860. # Try as branch first, fall back to commit
  861. if git ls-remote --heads "$KSU_REPO" "$KSU_INPUT" | grep -q .; then
  862. echo "Cloning KernelSU-Next from branch: $KSU_INPUT"
  863. git clone --branch "$KSU_INPUT" "$KSU_REPO" /tmp/kernelsu-next
  864. cd /tmp/kernelsu-next
  865. KSUN_COMMIT=$(git rev-parse HEAD)
  866. else
  867. echo "Cloning KernelSU-Next from dev-susfs branch, then checking out commit: $KSU_INPUT"
  868. git clone --branch dev-susfs "$KSU_REPO" /tmp/kernelsu-next
  869. cd /tmp/kernelsu-next
  870. git fetch --depth=50 origin "$KSU_INPUT"
  871. git checkout "$KSU_INPUT"
  872. KSUN_COMMIT="$KSU_INPUT"
  873. fi
  874. # The manager is built on the OFFICIAL KernelSU-Next repo
  875. # (pershoot's workflows are all disabled). Find the official dev
  876. # commit this fork's branch is synced with (merge-base of our
  877. # branch vs official dev), then link the manager build run for
  878. # that commit. pershoot's own dev branch is intentionally ignored.
  879. retry() {
  880. local n=0
  881. until [ "$n" -ge 5 ]; do
  882. "$@" && return 0
  883. n=$((n+1))
  884. echo "retry $n/5 failed for: $*" >&2
  885. sleep 5
  886. done
  887. return 1
  888. }
  889. OFFICIAL_KSU_REPO="https://github.com/KernelSU-Next/KernelSU-Next.git"
  890. retry git fetch "$OFFICIAL_KSU_REPO" dev
  891. STOCK_COMMIT=$(git merge-base HEAD FETCH_HEAD)
  892. echo "KSU Stock Commit (official sync point): $STOCK_COMMIT"
  893. # Extract version info — anchored to the official sync point
  894. # (STOCK_COMMIT), i.e. the commit the linked manager is built from.
  895. # Mirrors pershoot c0a2c5fd: count the merge-base with origin, not
  896. # the fork's extra commits, so the version shown in the release
  897. # notes matches what the kernel reports and the manager displays.
  898. COMMITS_COUNT=$(git rev-list --count "$STOCK_COMMIT")
  899. BASE_VERSION=30000
  900. KSU_VERSION=$(expr $COMMITS_COUNT "+" $BASE_VERSION)
  901. # Extract git tag at the sync point
  902. KSU_GIT_TAG=$(git describe --tags --abbrev=0 "$STOCK_COMMIT" 2>/dev/null || echo "no-tag")
  903. MANAGER_RUN_ID=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://api.github.com/repos/KernelSU-Next/KernelSU-Next/actions/workflows/build-manager-ci.yml/runs?head_sha=${STOCK_COMMIT}" | jq -r '.workflow_runs[0].id // empty')
  904. if [[ -n "${MANAGER_RUN_ID:-}" ]]; then
  905. KSU_MANAGER="https://github.com/KernelSU-Next/KernelSU-Next/actions/runs/${MANAGER_RUN_ID}"
  906. KSU_MANAGER_NOTE="built from stock dev commit ${STOCK_COMMIT:0:12} (exact match)"
  907. else
  908. # Validated up-front in resolve-sources; a release cannot proceed without
  909. # the exact manager run. Fail rather than link a mismatched manager.
  910. echo "ERROR: exact manager build no longer available for ${STOCK_COMMIT}" >&2
  911. exit 1
  912. fi
  913. echo "Commits count: $COMMITS_COUNT"
  914. echo "KSU Version: $KSU_VERSION"
  915. echo "KSU Git Tag: $KSU_GIT_TAG"
  916. echo "KSU Manager: $KSU_MANAGER"
  917. # Export as environment for next step
  918. echo "KSUN_BRANCH=$KSU_INPUT" >> $GITHUB_ENV
  919. echo "KSUN_COMMIT=$KSUN_COMMIT" >> $GITHUB_ENV
  920. echo "KSU_COMMITS_COUNT=$COMMITS_COUNT" >> $GITHUB_ENV
  921. echo "KSU_VERSION=$KSU_VERSION" >> $GITHUB_ENV
  922. echo "KSU_GIT_TAG=$KSU_GIT_TAG" >> $GITHUB_ENV
  923. echo "KSU_MANAGER=$KSU_MANAGER" >> $GITHUB_ENV
  924. echo "KSU_MANAGER_NOTE=${KSU_MANAGER_NOTE:-}" >> $GITHUB_ENV
  925. echo "MANAGER_RUN_ID=$MANAGER_RUN_ID" >> $GITHUB_ENV
  926. - name: Set release body
  927. run: |
  928. set -e
  929. RELEASE_NOTES_PATH="$RELEASE_NOTES"
  930. : > release_body.md
  931. python3 .github/scripts/render_release_body.py "$RELEASE_NOTES_PATH" > release_body.md
  932. - name: Publish release notes preview
  933. run: |
  934. {
  935. echo "## Release Notes Preview"
  936. echo
  937. cat release_body.md
  938. } >> "$GITHUB_STEP_SUMMARY"
  939. - name: Download KernelSU-Next Manager APK
  940. run: |
  941. set -euo pipefail
  942. echo "Downloading manager artifacts from run ${MANAGER_RUN_ID}"
  943. mkdir -p release-assets/manager
  944. # Official KernelSU-Next repo is public: list the build's artifacts,
  945. # download each zip, and include any APK as a release asset.
  946. curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  947. "https://api.github.com/repos/KernelSU-Next/KernelSU-Next/actions/runs/${MANAGER_RUN_ID}/artifacts" \
  948. | jq -r '.artifacts[]? | .id + " " + (.name|gsub(" "; "_"))' > /tmp/mgr_arts.txt
  949. if [[ ! -s /tmp/mgr_arts.txt ]]; then
  950. echo "ERROR: no artifacts found on manager run ${MANAGER_RUN_ID}" >&2
  951. exit 1
  952. fi
  953. while read -r ART_ID ART_NAME; do
  954. echo "-- downloading ${ART_NAME}
  955. curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  956. -L "https://api.github.com/repos/KernelSU-Next/KernelSU-Next/actions/artifacts/${ART_ID}/zip" \
  957. -o "/tmp/${ART_NAME}.zip"
  958. unzip -o -q "/tmp/${ART_NAME}.zip" -d "release-assets/manager/${ART_NAME}" || true
  959. done < /tmp/mgr_arts.txt
  960. # Normalize APKs so gh-release picks them up via *.apk glob
  961. find release-assets/manager -iname '*.apk' -exec cp -f {} release-assets/ \;
  962. find release-assets -maxdepth 1 -iname '*.apk' -printf 'staged: %f\n'
  963. - name: Download AnyKernel3 Artifacts
  964. uses: actions/download-artifact@v7
  965. with:
  966. path: release-assets
  967. pattern: '*-AnyKernel3'
  968. merge-multiple: false
  969. - name: Download NoMount metamodule
  970. uses: actions/download-artifact@v7
  971. with:
  972. path: release-assets
  973. pattern: '*NoMount-Metamodule'
  974. merge-multiple: false
  975. if-no-files-found: ignore
  976. - name: Zip AnyKernel3 for Release
  977. run: |
  978. set -e
  979. shopt -s nullglob
  980. for dir in release-assets/*-AnyKernel3; do
  981. zip_name="${dir}.zip"
  982. rm -f "$zip_name"
  983. cd "$dir"
  984. zip -r -q -9 "../$(basename "$zip_name")" .
  985. cd - >/dev/null
  986. done
  987. - name: Create Release
  988. uses: softprops/action-gh-release@v1
  989. with:
  990. tag_name: ${{ steps.tag.outputs.new_tag }}
  991. name: ${{ steps.release_meta.outputs.release_title }}
  992. body_path: release_body.md
  993. draft: false
  994. prerelease: ${{ inputs.release_type == 'Pre-Release' }}
  995. files: |
  996. release-assets/**/*.zip
  997. release-assets/**/*-boot*.img
  998. release-assets/*.apk