main.yml 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563
  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. use_cache:
  17. description: "Use ccache/bazel cache (restore/save)"
  18. type: boolean
  19. default: true
  20. bypass:
  21. description: "Apply kernel version-check bypass hack"
  22. type: boolean
  23. default: false
  24. kernel_build_version:
  25. description: "Kernel Version"
  26. type: choice
  27. options:
  28. - All
  29. - 5.10.x-android12
  30. - 5.10.x-android13
  31. - 5.15.x-android13
  32. - 5.15.x-android14
  33. - 6.1.x-android14
  34. - 6.6.x-android15
  35. - 6.12.x-android16
  36. default: All
  37. os_patch_level:
  38. description: "Patch date (YYYY-MM), kernel sublevel, lts, or All (no filter)"
  39. required: true
  40. type: string
  41. default: All
  42. brand_name:
  43. description: "Kernel Branding"
  44. type: string
  45. default: Wild
  46. commit_mode:
  47. description: "latest = branch tips, verified = audited pins, update = build latest then promote verified pins on success"
  48. required: false
  49. type: choice
  50. options:
  51. - latest
  52. - verified
  53. - update
  54. default: verified
  55. root_flavor:
  56. description: "Root Flavor"
  57. type: choice
  58. options:
  59. - KernelSU-Next
  60. - KernelSU
  61. - ReSukiSU
  62. - All
  63. default: All
  64. use_susfs:
  65. description: "SUSFS"
  66. type: boolean
  67. default: true
  68. use_nomount:
  69. description: "NoMount"
  70. type: boolean
  71. default: true
  72. use_bbg:
  73. description: "Baseband Guard"
  74. type: boolean
  75. default: true
  76. use_net:
  77. description: "Networking"
  78. type: boolean
  79. default: true
  80. use_ds:
  81. description: "DroidSpaces"
  82. type: boolean
  83. default: true
  84. use_ntsync:
  85. description: "NTSync"
  86. type: boolean
  87. default: true
  88. use_ptrace:
  89. description: "Ptrace Patch"
  90. type: boolean
  91. default: true
  92. use_unicode:
  93. description: "Unicode Fix"
  94. type: boolean
  95. default: true
  96. use_bpf:
  97. description: "BPF Stack (BTF + eBPF + FUSE-BPF)"
  98. type: boolean
  99. default: true
  100. use_perf:
  101. description: "Performance"
  102. type: boolean
  103. default: false
  104. test_release_notes:
  105. description: "Render release notes preview only (skip all builds + release)"
  106. type: boolean
  107. default: false
  108. jobs:
  109. resolve-sources:
  110. runs-on: ubuntu-latest
  111. outputs:
  112. nomount_commit: ${{ steps.resolve.outputs.nomount_commit }}
  113. kernelsu_commit: ${{ steps.resolve.outputs.kernelsu_commit }}
  114. kernelsu_branch: ${{ steps.resolve.outputs.kernelsu_branch }}
  115. root_flavor: ${{ steps.resolve.outputs.root_flavor }}
  116. root_commit: ${{ steps.resolve.outputs.root_commit }}
  117. root_matrix: ${{ steps.resolve.outputs.root_matrix }}
  118. susfs_commit_android12_5_10: ${{ steps.resolve.outputs.susfs_commit_android12_5_10 }}
  119. susfs_commit_android13_5_10: ${{ steps.resolve.outputs.susfs_commit_android13_5_10 }}
  120. susfs_commit_android13_5_15: ${{ steps.resolve.outputs.susfs_commit_android13_5_15 }}
  121. susfs_commit_android14_5_15: ${{ steps.resolve.outputs.susfs_commit_android14_5_15 }}
  122. susfs_commit_android14_6_1: ${{ steps.resolve.outputs.susfs_commit_android14_6_1 }}
  123. susfs_commit_android15_6_6: ${{ steps.resolve.outputs.susfs_commit_android15_6_6 }}
  124. susfs_commit_android16_6_12: ${{ steps.resolve.outputs.susfs_commit_android16_6_12 }}
  125. susfs_commit_android12_5_10_resukisu: ${{ steps.resolve.outputs.susfs_commit_android12_5_10_resukisu }}
  126. susfs_commit_android13_5_10_resukisu: ${{ steps.resolve.outputs.susfs_commit_android13_5_10_resukisu }}
  127. susfs_commit_android13_5_15_resukisu: ${{ steps.resolve.outputs.susfs_commit_android13_5_15_resukisu }}
  128. susfs_commit_android14_5_15_resukisu: ${{ steps.resolve.outputs.susfs_commit_android14_5_15_resukisu }}
  129. susfs_commit_android14_6_1_resukisu: ${{ steps.resolve.outputs.susfs_commit_android14_6_1_resukisu }}
  130. susfs_commit_android15_6_6_resukisu: ${{ steps.resolve.outputs.susfs_commit_android15_6_6_resukisu }}
  131. susfs_commit_android16_6_12_resukisu: ${{ steps.resolve.outputs.susfs_commit_android16_6_12_resukisu }}
  132. ksu_commit: ${{ steps.resolve.outputs.ksu_commit }}
  133. resukisu_commit: ${{ steps.resolve.outputs.resukisu_commit }}
  134. kernel_patches_commit: ${{ steps.resolve.outputs.kernel_patches_commit }}
  135. anykernel3_commit: ${{ steps.resolve.outputs.anykernel3_commit }}
  136. droidspaces_commit: ${{ steps.resolve.outputs.droidspaces_commit }}
  137. feature_set: ${{ steps.features.outputs.feature_set }}
  138. manager_list: ${{ steps.manager.outputs.manager_list }}
  139. steps:
  140. - name: Resolve latest component commits
  141. id: resolve
  142. shell: bash
  143. run: |
  144. set -euo pipefail
  145. # Commit resolution mode. 'latest' (default) resolves branch tips at
  146. # run time via git ls-remote. 'verified' uses the audited pins below for
  147. # NoMount, kernelsu/resukisu roots, and SUSFS; KernelSU-Next stays latest.
  148. COMMIT_MODE="${{ inputs.commit_mode || 'verified' }}"
  149. echo "commit_mode=${COMMIT_MODE}"
  150. retry() {
  151. local n=0
  152. until [ "$n" -ge 5 ]; do
  153. "$@" && return 0
  154. n=$((n+1))
  155. echo "retry $n/5 failed for: $*" >&2
  156. sleep 5
  157. done
  158. return 1
  159. }
  160. resolve_sha() { # repo ref
  161. retry git ls-remote "$1" "$2" | awk '{print $1; exit}'
  162. }
  163. emit() { # key value
  164. if [ -z "$2" ] || ! [[ "$2" =~ ^[0-9a-f]{40}$ ]]; then
  165. echo "Failed to resolve $1 (got: ${2:-empty})" >&2
  166. exit 1
  167. fi
  168. echo "$1=$2" >> "$GITHUB_OUTPUT"
  169. }
  170. # Audited pins (from root-variants.yml). Used only when commit_mode=verified.
  171. # KernelSU (tiann) and ReSukiSU always build at latest; only NoMount
  172. # and SUSFS pin in verified mode. KernelSU-Next stays latest.
  173. PIN_NOMOUNT="9a1431782f390453de54610fb91bdad03eab5b43"
  174. declare -A PIN_SUSFS=(
  175. [susfs_commit_android12_5_10]="ec785f47d49f7b3871ca9356f450411020af7017"
  176. [susfs_commit_android13_5_10]="ed3d6d9c9a2652e1c70f153f5358701e996d646c"
  177. [susfs_commit_android13_5_15]="bca0d2333c1a7d717e7278b019d7af7ba1d16005"
  178. [susfs_commit_android14_5_15]="d54b51724afa912c4c99bb99731354ce934d1889"
  179. [susfs_commit_android14_6_1]="4fc9c1898ea66f51847cdbc0d1473ea4ef525a70"
  180. [susfs_commit_android15_6_6]="937215cb3a1b1f333d764c366c7a49972fa8e7a0"
  181. [susfs_commit_android16_6_12]="7d91da2d2ce056d1abf378d9199aaf1072d37ab0"
  182. )
  183. # ReSukiSU keeps its own SUSFS pin set (currently the shared values,
  184. # including 6.1 = 0ff93279). KernelSU's set above already diverges on 6.1.
  185. declare -A PIN_SUSFS_RESUKISU=(
  186. [susfs_commit_android12_5_10]="ec785f47d49f7b3871ca9356f450411020af7017"
  187. [susfs_commit_android13_5_10]="ed3d6d9c9a2652e1c70f153f5358701e996d646c"
  188. [susfs_commit_android13_5_15]="bca0d2333c1a7d717e7278b019d7af7ba1d16005"
  189. [susfs_commit_android14_5_15]="d54b51724afa912c4c99bb99731354ce934d1889"
  190. [susfs_commit_android14_6_1]="4fc9c1898ea66f51847cdbc0d1473ea4ef525a70"
  191. [susfs_commit_android15_6_6]="937215cb3a1b1f333d764c366c7a49972fa8e7a0"
  192. [susfs_commit_android16_6_12]="7d91da2d2ce056d1abf378d9199aaf1072d37ab0"
  193. )
  194. pick() { # pinned repo ref -> pinned when verified, else latest
  195. local pinned="$1" repo="$2" ref="$3"
  196. if [ "$COMMIT_MODE" = "verified" ] && [ -n "$pinned" ]; then
  197. echo "$pinned"
  198. else
  199. resolve_sha "$repo" "$ref"
  200. fi
  201. }
  202. # NoMount (pinned in verified mode, else dev tip)
  203. emit nomount_commit "$(pick "$PIN_NOMOUNT" https://github.com/maxsteeel/nomount.git refs/heads/dev)"
  204. # KernelSU-Next manager - always built from the official repo's dev branch.
  205. # pershoot's fork is used only for the kernel SUSFS integration (root_commit below),
  206. # never for the manager. Manager always builds at latest.
  207. emit kernelsu_commit "$(resolve_sha https://github.com/KernelSU-Next/KernelSU-Next.git refs/heads/dev)"
  208. echo "kernelsu_branch=dev" >> "$GITHUB_OUTPUT"
  209. # Selected root implementation(s). Maps the human-facing choice
  210. # (KernelSU-Next / KernelSU / ReSukiSU / All) to internal root-setup
  211. # flavors (next / kernelsu / resukisu). Every build always has a root;
  212. # 'All' emits a matrix of all three root flavors with each resolved commit.
  213. #
  214. # kernelsu and resukisu always build at latest, like KernelSU-Next (next).
  215. KERNELSU_COMMIT="$(resolve_sha https://github.com/tiann/KernelSU.git refs/heads/main)"
  216. RESUKISU_COMMIT="$(resolve_sha https://github.com/ReSukiSU/ReSukiSU.git refs/heads/main)"
  217. emit ksu_commit "$KERNELSU_COMMIT"
  218. emit resukisu_commit "$RESUKISU_COMMIT"
  219. if [ "${{ inputs.use_susfs }}" = "true" ]; then
  220. NEXT_COMMIT="$(resolve_sha https://github.com/pershoot/KernelSU-Next.git refs/heads/dev-susfs)"
  221. else
  222. NEXT_COMMIT="$(resolve_sha https://github.com/KernelSU-Next/KernelSU-Next.git refs/heads/dev)"
  223. fi
  224. mk_entry() { # flavor variant commit
  225. printf '{"root_flavor":"%s","variant":"%s","root_commit":"%s"}' "$1" "$2" "$3"
  226. }
  227. case "${{ inputs.root_flavor || 'All' }}" in
  228. KernelSU)
  229. echo "root_flavor=kernelsu" >> "$GITHUB_OUTPUT"
  230. emit root_commit "$KERNELSU_COMMIT"
  231. ROOT_MATRIX="[$(mk_entry kernelsu KernelSU "$KERNELSU_COMMIT")]"
  232. ;;
  233. KernelSU-Next)
  234. echo "root_flavor=next" >> "$GITHUB_OUTPUT"
  235. emit root_commit "$NEXT_COMMIT"
  236. ROOT_MATRIX="[$(mk_entry next KernelSU-Next "$NEXT_COMMIT")]"
  237. ;;
  238. ReSukiSU)
  239. echo "root_flavor=resukisu" >> "$GITHUB_OUTPUT"
  240. emit root_commit "$RESUKISU_COMMIT"
  241. ROOT_MATRIX="[$(mk_entry resukisu ReSukiSU "$RESUKISU_COMMIT")]"
  242. ;;
  243. All)
  244. # Fan out to all three root variants, each with its pinned commit.
  245. ROOT_MATRIX="[$(mk_entry next KernelSU-Next "$NEXT_COMMIT"),$(mk_entry kernelsu KernelSU "$KERNELSU_COMMIT"),$(mk_entry resukisu ReSukiSU "$RESUKISU_COMMIT")]"
  246. echo "root_flavor=" >> "$GITHUB_OUTPUT"
  247. echo "root_commit=" >> "$GITHUB_OUTPUT"
  248. ;;
  249. *)
  250. echo "ERROR: unknown root_flavor '${{ inputs.root_flavor }}'" >&2
  251. exit 1
  252. ;;
  253. esac
  254. echo "root_matrix=$ROOT_MATRIX" >> "$GITHUB_OUTPUT"
  255. # SUSFS per supported Android/kernel branch (gitlab simonpunk/susfs4ksu).
  256. # Always emit the verified pinned SHA here; build.yml overrides to the
  257. # latest branch tip per-variant when the root flavor is KernelSU-Next.
  258. declare -A SUSFS_BRANCHES=(
  259. [susfs_commit_android12_5_10]="gki-android12-5.10"
  260. [susfs_commit_android13_5_10]="gki-android13-5.10"
  261. [susfs_commit_android13_5_15]="gki-android13-5.15"
  262. [susfs_commit_android14_5_15]="gki-android14-5.15"
  263. [susfs_commit_android14_6_1]="gki-android14-6.1"
  264. [susfs_commit_android15_6_6]="gki-android15-6.6"
  265. [susfs_commit_android16_6_12]="gki-android16-6.12"
  266. )
  267. for key in "${!SUSFS_BRANCHES[@]}"; do
  268. emit "$key" "$(pick "${PIN_SUSFS[$key]}" https://gitlab.com/simonpunk/susfs4ksu.git refs/heads/${SUSFS_BRANCHES[$key]})"
  269. # Per-flavor ReSukiSU SUSFS pin (same branch, own pin set).
  270. emit "${key}_resukisu" "$(pick "${PIN_SUSFS_RESUKISU[$key]}" https://gitlab.com/simonpunk/susfs4ksu.git refs/heads/${SUSFS_BRANCHES[$key]})"
  271. done
  272. # Tracked build helpers (resolved automatically, before the run)
  273. emit kernel_patches_commit "$(resolve_sha https://github.com/WildKernels/kernel_patches.git refs/heads/main)"
  274. emit anykernel3_commit "$(resolve_sha https://github.com/WildKernels/AnyKernel3.git refs/heads/gki-2.0)"
  275. emit droidspaces_commit "$(resolve_sha https://github.com/ravindu644/Droidspaces-OSS.git refs/heads/main)"
  276. echo "Resolved:"
  277. env | grep -E '^(nomount_commit|kernelsu_commit|kernelsu_branch|root_commit|susfs_commit_|kernel_patches_commit|anykernel3_commit|droidspaces_commit)' | sort || true
  278. - name: Resolve manager builds for selected root flavor(s)
  279. id: manager
  280. shell: bash
  281. run: |
  282. set -euo pipefail
  283. # Each selected root must ship ALL manager APK builds. KernelSU-Next publishes
  284. # its manager APKs (manager + manager-spoofed) from the official dev-tip
  285. # build-manager-ci run; kernelsu (KernelSU) and ReSukiSU expose theirs as
  286. # CI-run artifacts. All three are pinned to an exact commit.
  287. resolve_one() { # flavor
  288. local flavor="$1"
  289. local owner repo wf stock
  290. case "$flavor" in
  291. next)
  292. owner="KernelSU-Next"; repo="KernelSU-Next"; wf="build-manager-ci.yml"
  293. # Manager is built from the official dev-tip commit (grabbed at run start).
  294. stock="${{ steps.resolve.outputs.kernelsu_commit }}"
  295. run_id=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  296. -H "Authorization: Bearer ${{ github.token }}" \
  297. "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?head_sha=${stock}" \
  298. | jq -r '.workflow_runs[0].id // empty')
  299. if [[ -z "${run_id}" ]]; then
  300. echo "No manager build for dev tip ${stock}, falling back to latest successful run" >&2
  301. fb=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors -H "Authorization: Bearer ${{ github.token }}" "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?branch=dev&status=success&per_page=1")
  302. run_id=$(echo "$fb" | jq -r '.workflow_runs[0].id // empty')
  303. stock=$(echo "$fb" | jq -r '.workflow_runs[0].head_sha // empty')
  304. if [[ -n "$run_id" ]]; then echo "Fallback to ${stock} run ${run_id}" >&2; fi
  305. if [[ -z "${run_id}" ]]; then
  306. echo "ERROR: no ${repo} manager build exists for dev commit ${stock}" >&2
  307. exit 1
  308. fi
  309. fi
  310. printf '{"flavor":"%s","owner":"%s","repo":"%s","source":"run","run_id":%s,"stock":"%s","want":"manager,manager-spoofed"}' \
  311. "$flavor" "$owner" "$repo" "$run_id" "$stock"
  312. ;;
  313. kernelsu)
  314. owner="tiann"; repo="KernelSU"; wf="build-manager.yml"
  315. stock=$(git ls-remote https://github.com/tiann/KernelSU.git refs/heads/main | awk '{print $1; exit}')
  316. run_id=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  317. "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?head_sha=${stock}" \
  318. | jq -r '.workflow_runs[0].id // empty')
  319. if [[ -z "${run_id}" ]]; then
  320. echo "No manager build for stock ${stock}, falling back to latest successful run" >&2
  321. fb=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?branch=main&status=success&per_page=1")
  322. run_id=$(echo "$fb" | jq -r '.workflow_runs[0].id // empty')
  323. stock=$(echo "$fb" | jq -r '.workflow_runs[0].head_sha // empty')
  324. if [[ -n "$run_id" ]]; then echo "Fallback to ${stock} run ${run_id}" >&2; fi
  325. if [[ -z "${run_id}" ]]; then
  326. echo "ERROR: no ${repo} manager build exists for stock commit ${stock}" >&2
  327. exit 1
  328. fi
  329. fi
  330. printf '{"flavor":"%s","owner":"%s","repo":"%s","source":"run","run_id":%s,"stock":"%s","want":"manager"}' \
  331. "$flavor" "$owner" "$repo" "$run_id" "$stock"
  332. ;;
  333. resukisu)
  334. owner="ReSukiSU"; repo="ReSukiSU"; wf="build-manager.yml"
  335. stock=$(git ls-remote https://github.com/ReSukiSU/ReSukiSU.git refs/heads/main | awk '{print $1; exit}')
  336. run_id=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  337. "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?head_sha=${stock}" \
  338. | jq -r '.workflow_runs[0].id // empty')
  339. if [[ -z "${run_id}" ]]; then
  340. echo "No manager build for stock ${stock}, falling back to latest successful run" >&2
  341. fb=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?branch=main&status=success&per_page=1")
  342. run_id=$(echo "$fb" | jq -r '.workflow_runs[0].id // empty')
  343. stock=$(echo "$fb" | jq -r '.workflow_runs[0].head_sha // empty')
  344. if [[ -n "$run_id" ]]; then echo "Fallback to ${stock} run ${run_id}" >&2; fi
  345. if [[ -z "${run_id}" ]]; then
  346. echo "ERROR: no ${repo} manager build exists for stock commit ${stock}" >&2
  347. exit 1
  348. fi
  349. fi
  350. printf '{"flavor":"%s","owner":"%s","repo":"%s","source":"run","run_id":%s,"stock":"%s","want":"Manager-release,Spoofed-Manager-release"}' \
  351. "$flavor" "$owner" "$repo" "$run_id" "$stock"
  352. ;;
  353. *)
  354. echo "skipping unknown flavor: $flavor" >&2
  355. return 0
  356. ;;
  357. esac
  358. }
  359. FLAVORS="${{ inputs.root_flavor || 'All' }}"
  360. echo "Root flavor: ${FLAVORS}"
  361. LIST=""
  362. case "${FLAVORS}" in
  363. All) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one next)"$'\n'"$(resolve_one kernelsu)"$'\n'"$(resolve_one resukisu)") ;;
  364. KernelSU-Next) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one next)") ;;
  365. KernelSU) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one kernelsu)") ;;
  366. ReSukiSU) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one resukisu)") ;;
  367. *) echo "Skipping manager resolution (no root flavor / none)." ;;
  368. esac
  369. if [ -n "$LIST" ]; then
  370. echo "manager_list=$LIST" >> "$GITHUB_OUTPUT"
  371. echo "Manager sources resolved."
  372. else
  373. echo "manager_list=[]" >> "$GITHUB_OUTPUT"
  374. fi
  375. - name: Compose requested feature set
  376. id: features
  377. shell: bash
  378. run: |
  379. set -euo pipefail
  380. # Every build always has a root implementation (no 'none' option), so
  381. # SUSFS always has a root to hook into -- no extra guard needed.
  382. # A Release/Pre-Release ships whichever manger build(s) were resolved
  383. # in the manager step above (exact match per root flavor). No root
  384. # restriction applies here anymore -- KernelSU-Next, KernelSU, ReSukiSU,
  385. # or 'All' are all valid release targets.
  386. FEATURES=""
  387. [ "${{ inputs.use_susfs }}" = "true" ] && FEATURES="${FEATURES}SUSFS+"
  388. [ "${{ inputs.use_nomount }}" = "true" ] && FEATURES="${FEATURES}NoMount+"
  389. [ "${{ inputs.use_bbg }}" = "true" ] && FEATURES="${FEATURES}BBG+"
  390. [ "${{ inputs.use_net }}" = "true" ] && FEATURES="${FEATURES}NET+"
  391. [ "${{ inputs.use_ds }}" = "true" ] && FEATURES="${FEATURES}DS+"
  392. [ "${{ inputs.use_ntsync }}" = "true" ] && FEATURES="${FEATURES}NTSync+"
  393. [ "${{ inputs.use_ptrace }}" = "true" ] && FEATURES="${FEATURES}Ptrace+"
  394. [ "${{ inputs.use_unicode }}" = "true" ] && FEATURES="${FEATURES}Unicode+"
  395. [ "${{ inputs.use_bpf }}" = "true" ] && FEATURES="${FEATURES}BPF+"
  396. [ "${{ inputs.use_perf }}" = "true" ] && FEATURES="${FEATURES}Perf+"
  397. echo "feature_set=${FEATURES%+}" >> "$GITHUB_OUTPUT"
  398. - name: Clone shared source repos once
  399. if: inputs.use_nomount && !inputs.test_release_notes
  400. shell: bash
  401. run: |
  402. set -euo pipefail
  403. mkdir -p sources
  404. # NoMount kernel sources at the resolved commit (pinned or dev tip)
  405. git clone --no-checkout https://github.com/maxsteeel/nomount.git sources/nomount
  406. git -C sources/nomount fetch --depth=1 origin "${{ steps.resolve.outputs.nomount_commit }}"
  407. git -C sources/nomount checkout --detach "${{ steps.resolve.outputs.nomount_commit }}"
  408. [ "$(git -C sources/nomount rev-parse HEAD)" = "${{ steps.resolve.outputs.nomount_commit }}" ] || \
  409. { echo "NoMount clone HEAD mismatch" >&2; exit 1; }
  410. list-manager-apks:
  411. needs: resolve-sources
  412. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && needs.resolve-sources.outputs.manager_list != ''"
  413. runs-on: ubuntu-latest
  414. outputs:
  415. apk_list: ${{ steps.scan.outputs.apk_list }}
  416. steps:
  417. - uses: actions/checkout@v7
  418. - name: Download and scan all manager APKs
  419. id: scan
  420. shell: bash
  421. env:
  422. MANAGER_LIST: ${{ needs.resolve-sources.outputs.manager_list }}
  423. GH_TOKEN: ${{ github.token }}
  424. run: |
  425. set -euo pipefail
  426. MANAGER_LIST=$(echo "$MANAGER_LIST" | sed 's/^\[,/[/')
  427. if ! echo "$MANAGER_LIST" | jq empty 2>/dev/null; then
  428. echo "ERROR: Invalid MANAGER_LIST JSON"
  429. echo "Raw: $MANAGER_LIST"
  430. exit 0
  431. fi
  432. OUT_APKS="${GITHUB_WORKSPACE:-$PWD}/manager_apks"
  433. MANIFEST="${GITHUB_WORKSPACE:-$PWD}/manifest.txt"
  434. rm -rf "$OUT_APKS" "$MANIFEST"
  435. mkdir -p "$OUT_APKS"
  436. : > "$MANIFEST"
  437. echo "$MANAGER_LIST" | jq -c '.[]' | while read -r M; do
  438. OWNER=$(echo "$M" | jq -r .owner)
  439. REPO=$(echo "$M" | jq -r .repo)
  440. SRC=$(echo "$M" | jq -r .source)
  441. RUN_ID=$(echo "$M" | jq -r .run_id)
  442. WANT=$(echo "$M" | jq -r '.want // ""')
  443. DLDIR="dl_${REPO}"
  444. mkdir -p "$DLDIR"
  445. (
  446. cd "$DLDIR"
  447. if [ "$SRC" = "release" ]; then
  448. curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  449. "https://api.github.com/repos/${OWNER}/${REPO}/releases/latest" > rel.json
  450. for URL in $(jq -r '.assets[] | select(.name | endswith(".apk")) | .browser_download_url' rel.json); do
  451. NAME="${URL##*/}"
  452. curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors -o "$NAME" "$URL"
  453. cp -f "$NAME" "$OUT_APKS/$NAME"
  454. echo "$OWNER|$REPO|$NAME" >> "$MANIFEST"
  455. done
  456. else
  457. AJSON=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  458. -H "Authorization: Bearer ${GH_TOKEN}" \
  459. "https://api.github.com/repos/${OWNER}/${REPO}/actions/runs/${RUN_ID}/artifacts")
  460. if [ -z "$WANT" ]; then
  461. ART_IDS=$(echo "$AJSON" | jq -r '.artifacts[].id')
  462. else
  463. ART_IDS=$(echo "$AJSON" | jq -r --arg want "$WANT" '
  464. ($want | split(",")) as $wl |
  465. .artifacts[] | select(.name as $n | any($wl[]; $n == .)) | .id')
  466. fi
  467. echo "$ART_IDS" | while read -r AID; do
  468. [ -n "$AID" ] || continue
  469. curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
  470. -H "Authorization: Bearer ${GH_TOKEN}" \
  471. -o "_${AID}.zip" \
  472. "https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${AID}/zip"
  473. mkdir -p "_${AID}"
  474. unzip -o -q "_${AID}.zip" -d "_${AID}" || true
  475. rm -f "_${AID}.zip"
  476. for apk in _${AID}/*.apk; do
  477. [ -f "$apk" ] || continue
  478. NAME="$(basename "$apk")"
  479. cp -f "$apk" "$OUT_APKS/$NAME"
  480. echo "$OWNER|$REPO|$NAME" >> "$MANIFEST"
  481. done
  482. rm -rf "_${AID}"
  483. done
  484. fi
  485. )
  486. done
  487. # Filter ReSukiSU to only release universal and universal spoofed (drop debug/non-universal)
  488. if grep -q "ReSukiSU|ReSukiSU|" "$MANIFEST" 2>/dev/null; then
  489. tmp_manifest="${MANIFEST}.filtered"
  490. : > "$tmp_manifest"
  491. while IFS='|' read -r o r n; do
  492. [ -n "$n" ] || continue
  493. if [ "$o" = "ReSukiSU" ] && [ "$r" = "ReSukiSU" ]; then
  494. ln=$(echo "$n" | tr '[:upper:]' '[:lower:]')
  495. # keep only universal apks, drop debug
  496. if [[ "$ln" != *universal* ]]; then echo "Dropping ReSukiSU non-universal $n" >&2; rm -f "$OUT_APKS/$n"; continue; fi
  497. if [[ "$ln" == *debug* ]]; then echo "Dropping ReSukiSU debug $n" >&2; rm -f "$OUT_APKS/$n"; continue; fi
  498. # now only universal release and universal spoofed remain
  499. fi
  500. echo "$o|$r|$n" >> "$tmp_manifest"
  501. done < "$MANIFEST"
  502. mv "$tmp_manifest" "$MANIFEST"
  503. fi
  504. # Build the flat apk list from the manifest (single source of truth)
  505. APK_JSON="[]"
  506. while IFS='|' read -r o r n; do
  507. [ -n "$n" ] || continue
  508. APK_JSON=$(echo "$APK_JSON" | jq -c --arg o "$o" --arg r "$r" --arg n "$n" \
  509. '. + [{owner:$o,repo:$r,apk:$n}]')
  510. done < "$MANIFEST"
  511. echo "apk_list=${APK_JSON}" >> "$GITHUB_OUTPUT"
  512. echo "Found $(wc -l < "$MANIFEST") manager APKs"
  513. ls -1 "$OUT_APKS"
  514. - name: Upload all manager APKs as one cross-blob
  515. uses: actions/upload-artifact@v7
  516. with:
  517. name: manager-apks
  518. path: manager_apks/
  519. if-no-files-found: warn
  520. publish-manager-artifacts:
  521. needs: list-manager-apks
  522. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && needs.list-manager-apks.outputs.apk_list != ''"
  523. runs-on: ubuntu-latest
  524. strategy:
  525. fail-fast: false
  526. matrix:
  527. item: ${{ fromJson(needs.list-manager-apks.outputs.apk_list) }}
  528. steps:
  529. - uses: actions/checkout@v7
  530. - name: Download manager cross-blob
  531. uses: actions/download-artifact@v7
  532. with:
  533. name: manager-apks
  534. path: manager_apks
  535. merge-multiple: true
  536. - name: Upload ${{ matrix.item.apk }} as its own artifact
  537. uses: actions/upload-artifact@v7
  538. with:
  539. name: ${{ matrix.item.apk }}
  540. path: manager_apks/${{ matrix.item.apk }}
  541. if-no-files-found: error
  542. build-nomount-module:
  543. needs: resolve-sources
  544. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes"
  545. runs-on: ubuntu-latest
  546. steps:
  547. - uses: actions/checkout@v7
  548. if: inputs.use_nomount
  549. - name: Clone NoMount source at pinned commit
  550. id: clone
  551. if: inputs.use_nomount
  552. shell: bash
  553. run: |
  554. set -euo pipefail
  555. COMMIT="${{ needs.resolve-sources.outputs.nomount_commit }}"
  556. if ! [[ "$COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
  557. echo "NoMount commit must be a full 40-character SHA." >&2
  558. exit 2
  559. fi
  560. echo "NoMount commit: ${COMMIT}"
  561. echo "commit=${COMMIT}" >> "$GITHUB_OUTPUT"
  562. - name: Build NoMount metamodule (nm only, no .ko)
  563. if: inputs.use_nomount
  564. id: buildmod
  565. uses: ./.github/actions/nomount-metamodule
  566. with:
  567. commit: ${{ steps.clone.outputs.commit }}
  568. - name: Upload NoMount metamodule
  569. if: inputs.use_nomount
  570. uses: actions/upload-artifact@v7
  571. with:
  572. name: NoMount-Metamodule
  573. path: ${{ steps.buildmod.outputs.module_dir }}
  574. if-no-files-found: error
  575. compression-level: 0
  576. build-android12-5-10:
  577. needs:
  578. - resolve-sources
  579. - build-nomount-module
  580. - publish-manager-artifacts
  581. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == '5.10.x-android12')"
  582. strategy:
  583. fail-fast: false
  584. matrix:
  585. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  586. uses: ./.github/workflows/prepare.yml
  587. with:
  588. config_file: .github/config/android12-5.10.json
  589. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  590. susfs_commit: ${{ matrix.root.root_flavor == 'resukisu' && needs.resolve-sources.outputs.susfs_commit_android12_5_10_resukisu || needs.resolve-sources.outputs.susfs_commit_android12_5_10 }}
  591. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  592. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  593. root_flavor: ${{ matrix.root.root_flavor }}
  594. root_commit: ${{ matrix.root.root_commit }}
  595. variant: ${{ matrix.root.variant }}
  596. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  597. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  598. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  599. brand_name: ${{ inputs.brand_name }}
  600. os_patch_level: ${{ inputs.os_patch_level }}
  601. bypass: ${{ inputs.bypass }}
  602. use_cache: ${{ inputs.use_cache }}
  603. secrets: inherit
  604. build-android13-5-10:
  605. needs:
  606. - resolve-sources
  607. - build-nomount-module
  608. - publish-manager-artifacts
  609. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == '5.10.x-android13')"
  610. strategy:
  611. fail-fast: false
  612. matrix:
  613. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  614. uses: ./.github/workflows/prepare.yml
  615. with:
  616. config_file: .github/config/android13-5.10.json
  617. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  618. susfs_commit: ${{ matrix.root.root_flavor == 'resukisu' && needs.resolve-sources.outputs.susfs_commit_android13_5_10_resukisu || needs.resolve-sources.outputs.susfs_commit_android13_5_10 }}
  619. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  620. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  621. root_flavor: ${{ matrix.root.root_flavor }}
  622. root_commit: ${{ matrix.root.root_commit }}
  623. variant: ${{ matrix.root.variant }}
  624. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  625. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  626. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  627. brand_name: ${{ inputs.brand_name }}
  628. os_patch_level: ${{ inputs.os_patch_level }}
  629. bypass: ${{ inputs.bypass }}
  630. use_cache: ${{ inputs.use_cache }}
  631. secrets: inherit
  632. build-android13-5-15:
  633. needs:
  634. - resolve-sources
  635. - build-nomount-module
  636. - publish-manager-artifacts
  637. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == '5.15.x-android13')"
  638. strategy:
  639. fail-fast: false
  640. matrix:
  641. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  642. uses: ./.github/workflows/prepare.yml
  643. with:
  644. config_file: .github/config/android13-5.15.json
  645. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  646. susfs_commit: ${{ matrix.root.root_flavor == 'resukisu' && needs.resolve-sources.outputs.susfs_commit_android13_5_15_resukisu || needs.resolve-sources.outputs.susfs_commit_android13_5_15 }}
  647. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  648. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  649. root_flavor: ${{ matrix.root.root_flavor }}
  650. root_commit: ${{ matrix.root.root_commit }}
  651. variant: ${{ matrix.root.variant }}
  652. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  653. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  654. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  655. brand_name: ${{ inputs.brand_name }}
  656. os_patch_level: ${{ inputs.os_patch_level }}
  657. bypass: ${{ inputs.bypass }}
  658. use_cache: ${{ inputs.use_cache }}
  659. secrets: inherit
  660. build-android14-5-15:
  661. needs:
  662. - resolve-sources
  663. - build-nomount-module
  664. - publish-manager-artifacts
  665. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == '5.15.x-android14')"
  666. strategy:
  667. fail-fast: false
  668. matrix:
  669. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  670. uses: ./.github/workflows/prepare.yml
  671. with:
  672. config_file: .github/config/android14-5.15.json
  673. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  674. susfs_commit: ${{ matrix.root.root_flavor == 'resukisu' && needs.resolve-sources.outputs.susfs_commit_android14_5_15_resukisu || needs.resolve-sources.outputs.susfs_commit_android14_5_15 }}
  675. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  676. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  677. root_flavor: ${{ matrix.root.root_flavor }}
  678. root_commit: ${{ matrix.root.root_commit }}
  679. variant: ${{ matrix.root.variant }}
  680. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  681. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  682. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  683. brand_name: ${{ inputs.brand_name }}
  684. os_patch_level: ${{ inputs.os_patch_level }}
  685. bypass: ${{ inputs.bypass }}
  686. use_cache: ${{ inputs.use_cache }}
  687. secrets: inherit
  688. build-android14-6-1:
  689. needs:
  690. - resolve-sources
  691. - build-nomount-module
  692. - publish-manager-artifacts
  693. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == '6.1.x-android14')"
  694. strategy:
  695. fail-fast: false
  696. matrix:
  697. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  698. uses: ./.github/workflows/prepare.yml
  699. with:
  700. config_file: .github/config/android14-6.1.json
  701. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  702. susfs_commit: ${{ matrix.root.root_flavor == 'resukisu' && needs.resolve-sources.outputs.susfs_commit_android14_6_1_resukisu || needs.resolve-sources.outputs.susfs_commit_android14_6_1 }}
  703. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  704. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  705. root_flavor: ${{ matrix.root.root_flavor }}
  706. root_commit: ${{ matrix.root.root_commit }}
  707. variant: ${{ matrix.root.variant }}
  708. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  709. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  710. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  711. brand_name: ${{ inputs.brand_name }}
  712. os_patch_level: ${{ inputs.os_patch_level }}
  713. bypass: ${{ inputs.bypass }}
  714. use_cache: ${{ inputs.use_cache }}
  715. secrets: inherit
  716. build-android15-6-6:
  717. needs:
  718. - resolve-sources
  719. - build-nomount-module
  720. - publish-manager-artifacts
  721. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == '6.6.x-android15')"
  722. strategy:
  723. fail-fast: false
  724. matrix:
  725. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  726. uses: ./.github/workflows/prepare.yml
  727. with:
  728. config_file: .github/config/android15-6.6.json
  729. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  730. susfs_commit: ${{ matrix.root.root_flavor == 'resukisu' && needs.resolve-sources.outputs.susfs_commit_android15_6_6_resukisu || needs.resolve-sources.outputs.susfs_commit_android15_6_6 }}
  731. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  732. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  733. root_flavor: ${{ matrix.root.root_flavor }}
  734. root_commit: ${{ matrix.root.root_commit }}
  735. variant: ${{ matrix.root.variant }}
  736. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  737. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  738. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  739. brand_name: ${{ inputs.brand_name }}
  740. os_patch_level: ${{ inputs.os_patch_level }}
  741. bypass: ${{ inputs.bypass }}
  742. use_cache: ${{ inputs.use_cache }}
  743. secrets: inherit
  744. build-android16-6-12:
  745. needs:
  746. - resolve-sources
  747. - build-nomount-module
  748. - publish-manager-artifacts
  749. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == '6.12.x-android16')"
  750. strategy:
  751. fail-fast: false
  752. matrix:
  753. root: ${{ fromJson(needs.resolve-sources.outputs.root_matrix) }}
  754. uses: ./.github/workflows/prepare.yml
  755. with:
  756. config_file: .github/config/android16-6.12.json
  757. feature_set: ${{ needs.resolve-sources.outputs.feature_set }}
  758. susfs_commit: ${{ matrix.root.root_flavor == 'resukisu' && needs.resolve-sources.outputs.susfs_commit_android16_6_12_resukisu || needs.resolve-sources.outputs.susfs_commit_android16_6_12 }}
  759. nomount_commit: ${{ needs.resolve-sources.outputs.nomount_commit }}
  760. kernel_patches_commit: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  761. root_flavor: ${{ matrix.root.root_flavor }}
  762. root_commit: ${{ matrix.root.root_commit }}
  763. variant: ${{ matrix.root.variant }}
  764. ksu_branch: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  765. anykernel3_commit: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  766. droidspaces_commit: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  767. brand_name: ${{ inputs.brand_name }}
  768. os_patch_level: ${{ inputs.os_patch_level }}
  769. bypass: ${{ inputs.bypass }}
  770. use_cache: ${{ inputs.use_cache }}
  771. secrets: inherit
  772. update-verified-pins:
  773. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && inputs.commit_mode == 'update'"
  774. runs-on: ubuntu-latest
  775. permissions:
  776. actions: read
  777. contents: write
  778. needs:
  779. - resolve-sources
  780. - build-android12-5-10
  781. - build-android13-5-10
  782. - build-android13-5-15
  783. - build-android14-5-15
  784. - build-android14-6-1
  785. - build-android15-6-6
  786. - build-android16-6-12
  787. steps:
  788. - uses: actions/checkout@v7
  789. with:
  790. ref: ${{ github.ref }}
  791. token: ${{ github.token }}
  792. - name: Promote verified pins on success
  793. id: promote
  794. shell: bash
  795. run: |
  796. set -euo pipefail
  797. python3 .github/scripts/update_verified_pins.py
  798. env:
  799. GH_TOKEN: ${{ github.token }}
  800. NOMOUNT_SHA: ${{ needs.resolve-sources.outputs.nomount_commit }}
  801. APPROVE_NOMOUNT: ${{ inputs.use_nomount }}
  802. - name: Commit promoted pins
  803. if: always() && steps.promote.conclusion == 'success'
  804. shell: bash
  805. run: |
  806. set -euo pipefail
  807. git config user.name "github-actions[bot]"
  808. git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
  809. STAMP="$(date -u +%Y-%m-%dT%H%M%SZ)"
  810. git add .github/workflows/main.yml .github/pins/history
  811. if git diff --cached --quiet; then
  812. echo "No pin changes to commit."
  813. exit 0
  814. fi
  815. git commit -m "chore(pins): promote verified commits to latest built (${STAMP})"
  816. git push
  817. rej:
  818. if: "github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && always()"
  819. runs-on: ubuntu-latest
  820. permissions:
  821. actions: read
  822. contents: read
  823. needs:
  824. - build-android12-5-10
  825. - build-android13-5-10
  826. - build-android13-5-15
  827. - build-android14-5-15
  828. - build-android14-6-1
  829. - build-android15-6-6
  830. - build-android16-6-12
  831. steps:
  832. - name: Download Misc Artifacts
  833. uses: actions/download-artifact@v7
  834. with:
  835. path: ./downloaded-artifacts
  836. pattern: '*-Rejects'
  837. merge-multiple: false
  838. - name: Process Reject Artifacts
  839. run: |
  840. mkdir -p aio-rejects
  841. # Iterate over Rejects artifacts in downloaded-artifacts
  842. for dir in ./downloaded-artifacts/*-Rejects; do
  843. [ -d "$dir" ] || continue
  844. dirname=$(basename "$dir")
  845. original_name=${dirname%-Rejects}
  846. source_dir="$dir"
  847. if [ -d "$dir/patch-rejects" ]; then
  848. source_dir="$dir/patch-rejects"
  849. fi
  850. mapfile -t rej_files < <(find "$source_dir" -type f -name '*.rej' | sort)
  851. filtered_rej_files=()
  852. for rej_file in "${rej_files[@]}"; do
  853. rel_rej="${rej_file#${source_dir}/}"
  854. rel_name="$(basename "$rel_rej")"
  855. if [ "$rel_rej" = "common/mm/rmap.c.rej" ] || [ "$rel_name" = "i2c-nomadik.c.rej" ]; then
  856. echo "Skipping $rel_rej because it is an ignored reject file"
  857. continue
  858. fi
  859. filtered_rej_files+=("$rej_file")
  860. done
  861. if [ "${#filtered_rej_files[@]}" -eq 0 ]; then
  862. echo "Skipping $dirname because it only contains ignored reject files"
  863. continue
  864. fi
  865. mkdir -p "aio-rejects/$original_name"
  866. if [ -d "$dir/patch-rejects" ]; then
  867. cp -r "$dir/patch-rejects/." "aio-rejects/$original_name/"
  868. else
  869. cp -r "$dir/." "aio-rejects/$original_name/"
  870. fi
  871. find "aio-rejects/$original_name" -type f -name 'i2c-nomadik.c.rej' -delete
  872. done
  873. if [ "$(ls -A aio-rejects)" ]; then
  874. cd aio-rejects
  875. zip -r -q -9 ../AIO-REJ.zip .
  876. cd ..
  877. else
  878. echo "No rejects found to upload."
  879. fi
  880. - name: Rejects Summary
  881. run: |
  882. echo "Reject artifacts were collected locally only and are not uploaded." >> "$GITHUB_STEP_SUMMARY"
  883. - name: Upload AIO-REJ Artifact
  884. uses: actions/upload-artifact@v7
  885. with:
  886. name: AIO-REJ
  887. path: AIO-REJ.zip
  888. if-no-files-found: ignore
  889. release:
  890. runs-on: ubuntu-latest
  891. if: github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && (inputs.release_type == 'Pre-Release' || inputs.release_type == 'Release')
  892. permissions:
  893. contents: write
  894. needs:
  895. - build-android12-5-10
  896. - build-android13-5-10
  897. - build-android13-5-15
  898. - build-android14-5-15
  899. - build-android14-6-1
  900. - build-android15-6-6
  901. - build-android16-6-12
  902. - build-nomount-module
  903. - publish-manager-artifacts
  904. env:
  905. GH_TOKEN: ${{ github.token }}
  906. outputs:
  907. new_tag: ${{ steps.tag.outputs.new_tag }}
  908. steps:
  909. - name: Validate Selected Builds
  910. run: |
  911. set -euo pipefail
  912. failed=0
  913. current_type="${{ inputs.kernel_build_version }}"
  914. check_selected() {
  915. local target="$1"
  916. local job_name="$2"
  917. local result="$3"
  918. if [[ "$current_type" == "all" || "$current_type" == "$target" ]]; then
  919. if [[ "$result" != "success" ]]; then
  920. echo "Required job $job_name did not succeed (result: $result)"
  921. failed=1
  922. fi
  923. fi
  924. }
  925. check_selected "5.10.x-android12" "build-android12-5-10" "${{ needs.build-android12-5-10.result }}"
  926. check_selected "5.10.x-android13" "build-android13-5-10" "${{ needs.build-android13-5-10.result }}"
  927. check_selected "5.15.x-android13" "build-android13-5-15" "${{ needs.build-android13-5-15.result }}"
  928. check_selected "5.15.x-android14" "build-android14-5-15" "${{ needs.build-android14-5-15.result }}"
  929. check_selected "6.1.x-android14" "build-android14-6-1" "${{ needs.build-android14-6-1.result }}"
  930. check_selected "6.6.x-android15" "build-android15-6-6" "${{ needs.build-android15-6-6.result }}"
  931. check_selected "6.12.x-android16" "build-android16-6-12" "${{ needs.build-android16-6-12.result }}"
  932. if [[ "$failed" -ne 0 ]]; then
  933. exit 1
  934. fi
  935. - name: Free Disk Space
  936. if: true
  937. uses: endersonmenezes/free-disk-space@v3 # Use @main for latest, @v3 for stable
  938. with:
  939. remove_android: true
  940. remove_dotnet: true
  941. remove_haskell: true
  942. remove_tool_cache: true
  943. remove_swap: true
  944. remove_packages: "azure-cli google-cloud-cli microsoft-edge-stable google-chrome-stable firefox postgresql* temurin-* *llvm* mysql* dotnet-sdk-*"
  945. remove_packages_one_command: true
  946. 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"
  947. rm_cmd: "rmz" # Use 'rmz' for faster deletion (default: 'rm')
  948. rmz_version: "3.1.1" # Required when rm_cmd is 'rmz'
  949. testing: false
  950. - name: Checkout code
  951. uses: actions/checkout@v7
  952. - name: Generate New Tag
  953. id: tag
  954. if: inputs.release_type != 'Action'
  955. run: |
  956. 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)
  957. LATEST_REV=0
  958. for TAG in "${RELEASE_TAGS[@]}"; do
  959. REV="${TAG#r}"
  960. if [[ "$REV" =~ ^[0-9]+$ && "$REV" -gt "$LATEST_REV" ]]; then
  961. LATEST_REV="$REV"
  962. fi
  963. done
  964. if [[ "$LATEST_REV" -gt 0 ]]; then
  965. NEW_TAG="r$((LATEST_REV + 1))"
  966. else
  967. NEW_TAG="r1"
  968. fi
  969. if [[ ${#RELEASE_TAGS[@]} -gt 0 ]]; then
  970. echo "Latest r-tag: r${LATEST_REV}"
  971. else
  972. echo "No existing r-tags found; starting at r1"
  973. fi
  974. echo "New tag: $NEW_TAG"
  975. echo "NEW_TAG=${NEW_TAG}" >> $GITHUB_ENV
  976. echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
  977. - name: Set Release Title
  978. id: release_meta
  979. run: |
  980. set -euo pipefail
  981. # Format release title: convert rN -> Release N
  982. if [[ "${NEW_TAG}" =~ ^r([0-9]+)$ ]]; then
  983. REV_NUM="${BASH_REMATCH[1]}"
  984. release_title="Release ${REV_NUM}"
  985. else
  986. release_title="Release ${NEW_TAG}"
  987. fi
  988. echo "release_title=$release_title" >> "$GITHUB_OUTPUT"
  989. - name: Extract KernelSU Version Info
  990. id: ksu_version
  991. run: |
  992. set -euo pipefail
  993. KSU_REPO="https://github.com/pershoot/KernelSU-Next.git"
  994. KSU_INPUT="dev-susfs"
  995. # Try as branch first, fall back to commit
  996. if git ls-remote --heads "$KSU_REPO" "$KSU_INPUT" | grep -q .; then
  997. echo "Cloning KernelSU-Next from branch: $KSU_INPUT"
  998. git clone --branch "$KSU_INPUT" "$KSU_REPO" /tmp/kernelsu-next
  999. cd /tmp/kernelsu-next
  1000. KSUN_COMMIT=$(git rev-parse HEAD)
  1001. else
  1002. echo "Cloning KernelSU-Next from dev-susfs branch, then checking out commit: $KSU_INPUT"
  1003. git clone --branch dev-susfs "$KSU_REPO" /tmp/kernelsu-next
  1004. cd /tmp/kernelsu-next
  1005. git fetch --depth=50 origin "$KSU_INPUT"
  1006. git checkout "$KSU_INPUT"
  1007. KSUN_COMMIT="$KSU_INPUT"
  1008. fi
  1009. # The manager is built on the OFFICIAL KernelSU-Next repo
  1010. # (pershoot's workflows are all disabled). Find the official dev
  1011. # commit this fork's branch is synced with (merge-base of our
  1012. # branch vs official dev), then link the manager build run for
  1013. # that commit. pershoot's own dev branch is intentionally ignored.
  1014. retry() {
  1015. local n=0
  1016. until [ "$n" -ge 5 ]; do
  1017. "$@" && return 0
  1018. n=$((n+1))
  1019. echo "retry $n/5 failed for: $*" >&2
  1020. sleep 5
  1021. done
  1022. return 1
  1023. }
  1024. OFFICIAL_KSU_REPO="https://github.com/KernelSU-Next/KernelSU-Next.git"
  1025. retry git fetch "$OFFICIAL_KSU_REPO" dev
  1026. STOCK_COMMIT=$(git merge-base HEAD FETCH_HEAD)
  1027. echo "KSU Stock Commit (official sync point): $STOCK_COMMIT"
  1028. # Extract version info - anchored to the official sync point
  1029. # (STOCK_COMMIT), i.e. the commit the linked manager is built from.
  1030. # Mirrors pershoot c0a2c5fd: count the merge-base with origin, not
  1031. # the fork's extra commits, so the version shown in the release
  1032. # notes matches what the kernel reports and the manager displays.
  1033. COMMITS_COUNT=$(git rev-list --count "$STOCK_COMMIT")
  1034. BASE_VERSION=30000
  1035. KSU_VERSION=$(expr $COMMITS_COUNT "+" $BASE_VERSION)
  1036. # Extract git tag at the sync point
  1037. KSU_GIT_TAG=$(git describe --tags --abbrev=0 "$STOCK_COMMIT" 2>/dev/null || echo "no-tag")
  1038. 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')
  1039. if [[ -n "${MANAGER_RUN_ID:-}" ]]; then
  1040. KSU_MANAGER="https://github.com/KernelSU-Next/KernelSU-Next/actions/runs/${MANAGER_RUN_ID}"
  1041. KSU_MANAGER_NOTE="built from stock dev commit ${STOCK_COMMIT:0:12} (exact match)"
  1042. else
  1043. # Validated up-front in resolve-sources; a release cannot proceed without
  1044. # the exact manager run. Fail rather than link a mismatched manager.
  1045. echo "ERROR: exact manager build no longer available for ${STOCK_COMMIT}" >&2
  1046. exit 1
  1047. fi
  1048. echo "Commits count: $COMMITS_COUNT"
  1049. echo "KSU Version: $KSU_VERSION"
  1050. echo "KSU Git Tag: $KSU_GIT_TAG"
  1051. echo "KSU Manager: $KSU_MANAGER"
  1052. # Export as environment for next step
  1053. echo "KSUN_BRANCH=$KSU_INPUT" >> $GITHUB_ENV
  1054. echo "KSUN_COMMIT=$KSUN_COMMIT" >> $GITHUB_ENV
  1055. echo "KSU_COMMITS_COUNT=$COMMITS_COUNT" >> $GITHUB_ENV
  1056. echo "KSU_VERSION=$KSU_VERSION" >> $GITHUB_ENV
  1057. echo "KSU_GIT_TAG=$KSU_GIT_TAG" >> $GITHUB_ENV
  1058. echo "KSU_MANAGER=$KSU_MANAGER" >> $GITHUB_ENV
  1059. echo "KSU_MANAGER_NOTE=${KSU_MANAGER_NOTE:-}" >> $GITHUB_ENV
  1060. echo "MANAGER_RUN_ID=$MANAGER_RUN_ID" >> $GITHUB_ENV
  1061. - name: Set release body
  1062. env:
  1063. ROOT_FLAVOR: ${{ inputs.root_flavor }}
  1064. FEATURE_SET: ${{ needs.resolve-sources.outputs.feature_set }}
  1065. MANAGER_LIST: ${{ needs.resolve-sources.outputs.manager_list }}
  1066. ROOT_MATRIX: ${{ needs.resolve-sources.outputs.root_matrix }}
  1067. USE_SUSFS: ${{ inputs.use_susfs }}
  1068. USE_NOMOUNT: ${{ inputs.use_nomount }}
  1069. USE_BBG: ${{ inputs.use_bbg }}
  1070. USE_DS: ${{ inputs.use_ds }}
  1071. USE_NET: ${{ inputs.use_net }}
  1072. USE_NTSYNC: ${{ inputs.use_ntsync }}
  1073. USE_PTRACE: ${{ inputs.use_ptrace }}
  1074. USE_UNICODE: ${{ inputs.use_unicode }}
  1075. USE_BPF: ${{ inputs.use_bpf }}
  1076. USE_PERF: ${{ inputs.use_perf }}
  1077. USE_CACHE: ${{ inputs.use_cache }}
  1078. KERNEL_BUILD_VERSION: ${{ inputs.kernel_build_version }}
  1079. NOMOUNT_COMMIT: ${{ needs.resolve-sources.outputs.nomount_commit }}
  1080. KSU_COMMIT: ${{ needs.resolve-sources.outputs.ksu_commit }}
  1081. RESUKISU_COMMIT: ${{ needs.resolve-sources.outputs.resukisu_commit }}
  1082. KERNELSU_COMMIT: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  1083. KERNEL_PATCHES_COMMIT: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  1084. ANYKERNEL3_COMMIT: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  1085. DROIDSPACES_COMMIT: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  1086. SUSFS_ANDROID12_5_10: ${{ needs.resolve-sources.outputs.susfs_commit_android12_5_10 }}
  1087. SUSFS_ANDROID13_5_10: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_10 }}
  1088. SUSFS_ANDROID13_5_15: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_15 }}
  1089. SUSFS_ANDROID14_5_15: ${{ needs.resolve-sources.outputs.susfs_commit_android14_5_15 }}
  1090. SUSFS_ANDROID14_6_1: ${{ needs.resolve-sources.outputs.susfs_commit_android14_6_1 }}
  1091. SUSFS_ANDROID15_6_6: ${{ needs.resolve-sources.outputs.susfs_commit_android15_6_6 }}
  1092. SUSFS_ANDROID16_6_12: ${{ needs.resolve-sources.outputs.susfs_commit_android16_6_12 }}
  1093. SUSFS_ANDROID12_5_10_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android12_5_10_resukisu }}
  1094. SUSFS_ANDROID13_5_10_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_10_resukisu }}
  1095. SUSFS_ANDROID13_5_15_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_15_resukisu }}
  1096. SUSFS_ANDROID14_5_15_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android14_5_15_resukisu }}
  1097. SUSFS_ANDROID14_6_1_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android14_6_1_resukisu }}
  1098. SUSFS_ANDROID15_6_6_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android15_6_6_resukisu }}
  1099. SUSFS_ANDROID16_6_12_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android16_6_12_resukisu }}
  1100. run: |
  1101. set -e
  1102. : > release_body.md
  1103. python3 - <<'PYEOF' > release_body.md
  1104. import json, os
  1105. def env(n,d=""): return os.environ.get(n,d).strip() or d
  1106. def parse_managers(raw):
  1107. try: return json.loads(raw) if raw.strip() else []
  1108. except: return []
  1109. root = env("ROOT_FLAVOR","All")
  1110. feats=[]
  1111. if env("USE_SUSFS","true")=="true": feats.append("SUSFS")
  1112. if env("USE_NOMOUNT","true")=="true": feats.append("NoMount")
  1113. if env("USE_BBG","true")=="true": feats.append("Baseband Guard")
  1114. if env("USE_DS","true")=="true": feats.append("DroidSpaces-OSS")
  1115. if env("USE_NET","true")=="true": feats.append("Networking")
  1116. if env("USE_NTSYNC","true")=="true": feats.append("NTSync")
  1117. if env("USE_PTRACE","true")=="true": feats.append("Ptrace Leak Fix")
  1118. if env("USE_UNICODE","true")=="true": feats.append("Unicode Fix")
  1119. if env("USE_BPF","true")=="true": feats.append("BTF / eBPF / FUSE-BPF")
  1120. if env("USE_PERF","false")=="true": feats.append("Performance Tuning")
  1121. lines=[]
  1122. lines.append("# Wild Kernels for GKI2 Devices")
  1123. lines.append("")
  1124. lines.append("## Root")
  1125. lines.append("")
  1126. ksun_branch=env("KSUN_BRANCH","dev-susfs")
  1127. ksun_commit=env("KSUN_COMMIT","")
  1128. ksu_commit=env("KSU_COMMIT","")
  1129. resukisu_commit=env("RESUKISU_COMMIT","")
  1130. kernelsu_commit=env("KERNELSU_COMMIT","")
  1131. nm=env("NOMOUNT_COMMIT","")
  1132. kp=env("KERNEL_PATCHES_COMMIT","")
  1133. ak=env("ANYKERNEL3_COMMIT","")
  1134. ds=env("DROIDSPACES_COMMIT","")
  1135. _rmatrix={}
  1136. try: _rmatrix={m.get("root_flavor",""):m.get("root_commit","") for m in json.loads(env("ROOT_MATRIX","[]")) if isinstance(m,dict)}
  1137. except: pass
  1138. if env("USE_SUSFS","true")=="true":
  1139. _next_url="https://github.com/pershoot/KernelSU-Next/commit/"
  1140. else:
  1141. _next_url="https://github.com/KernelSU-Next/KernelSU-Next/commit/"
  1142. _root_urls={"next":_next_url,"kernelsu":"https://github.com/tiann/KernelSU/commit/","resukisu":"https://github.com/ReSukiSU/ReSukiSU/commit/"}
  1143. _root_commits={"next":_rmatrix.get("next","") or kernelsu_commit,"kernelsu":_rmatrix.get("kernelsu","") or ksu_commit,"resukisu":_rmatrix.get("resukisu","") or resukisu_commit}
  1144. _flavors0=[m.get("flavor","") for m in parse_managers(env("MANAGER_LIST","")) if m.get("flavor","")]
  1145. if not _flavors0:
  1146. _flavors0={"KernelSU-Next":["next"],"KernelSU":["kernelsu"],"ReSukiSU":["resukisu"],"All":["next","kernelsu","resukisu"]}.get(root,["next","kernelsu","resukisu"])
  1147. _labels0={"next":"KernelSU-Next","kernelsu":"KernelSU","resukisu":"ReSukiSU"}
  1148. _rc=[]
  1149. for _fl0 in _flavors0:
  1150. _c=_root_commits.get(_fl0,"")
  1151. if _c and len(_c)==40: _rc.append(f"{_labels0.get(_fl0,_fl0)} [`{_c[:7]}`]({_root_urls[_fl0]}{_c})")
  1152. elif _c: _rc.append(f"{_labels0.get(_fl0,_fl0)} `{_c}`")
  1153. for _r in _rc: lines.append(f"- {_r}")
  1154. lines.append("")
  1155. lines.append("## Features")
  1156. lines.append("")
  1157. _kp_url="https://github.com/WildKernels/kernel_patches/commit/"
  1158. _feat_commit={"NoMount":("https://github.com/maxsteeel/nomount/commit/",nm),"Baseband Guard":(_kp_url,kp),"DroidSpaces-OSS":("https://github.com/ravindu644/Droidspaces-OSS/commit/",ds)}
  1159. for f in feats:
  1160. _u,_c=_feat_commit.get(f,("",""))
  1161. if _c and len(_c)==40: lines.append(f"- {f} [`{_c[:7]}`]({_u}{_c})")
  1162. elif _c: lines.append(f"- {f} `{_c}`")
  1163. else: lines.append(f"- {f}")
  1164. if not feats: lines.append("- (none)")
  1165. kp=env("KERNEL_PATCHES_COMMIT","")
  1166. ak=env("ANYKERNEL3_COMMIT","")
  1167. ds=env("DROIDSPACES_COMMIT","")
  1168. lines.append("")
  1169. lines.append("## SUSFS commits")
  1170. lines.append("")
  1171. all_variants=["android12-5.10","android13-5.10","android13-5.15","android14-5.15","android14-6.1","android15-6.6","android16-6.12"]
  1172. def kbv_to_variant(kbv):
  1173. if ".x-android" in kbv:
  1174. kv, av = kbv.split(".x-android", 1)
  1175. v = f"android{av}-{kv}"
  1176. return v if v in all_variants else ""
  1177. return ""
  1178. kbv=env("KERNEL_BUILD_VERSION","All")
  1179. _sel=kbv_to_variant(kbv)
  1180. variants=[_sel] if _sel else list(all_variants)
  1181. sus_map={"android12-5.10":env("SUSFS_ANDROID12_5_10",""),"android13-5.10":env("SUSFS_ANDROID13_5_10",""),"android13-5.15":env("SUSFS_ANDROID13_5_15",""),"android14-5.15":env("SUSFS_ANDROID14_5_15",""),"android14-6.1":env("SUSFS_ANDROID14_6_1",""),"android15-6.6":env("SUSFS_ANDROID15_6_6",""),"android16-6.12":env("SUSFS_ANDROID16_6_12","")}
  1182. sus_map_resukisu={"android12-5.10":env("SUSFS_ANDROID12_5_10_RESUKISU",""),"android13-5.10":env("SUSFS_ANDROID13_5_10_RESUKISU",""),"android13-5.15":env("SUSFS_ANDROID13_5_15_RESUKISU",""),"android14-5.15":env("SUSFS_ANDROID14_5_15_RESUKISU",""),"android14-6.1":env("SUSFS_ANDROID14_6_1_RESUKISU",""),"android15-6.6":env("SUSFS_ANDROID15_6_6_RESUKISU",""),"android16-6.12":env("SUSFS_ANDROID16_6_12_RESUKISU","")}
  1183. _flavors=[m.get("flavor","") for m in parse_managers(env("MANAGER_LIST","")) if m.get("flavor","")]
  1184. if not _flavors:
  1185. _flavors={"KernelSU-Next":["next"],"KernelSU":["kernelsu"],"ReSukiSU":["resukisu"],"All":["next","kernelsu","resukisu"]}.get(root,["next","kernelsu","resukisu"])
  1186. _labels={"next":"KernelSU-Next","kernelsu":"KernelSU","resukisu":"ReSukiSU"}
  1187. _susfs_url="https://gitlab.com/simonpunk/susfs4ksu"
  1188. lines.append("| SUSFS branch |" + "".join(f" {_labels.get(_fl,_fl)} |" for _fl in _flavors))
  1189. lines.append("|---" * (len(_flavors) + 1) + "|")
  1190. for v in variants:
  1191. _row=f"| gki-{v} |"
  1192. for _fl in _flavors:
  1193. _sm=sus_map_resukisu if _fl=="resukisu" else sus_map
  1194. sha=_sm.get(v,"")
  1195. sha_s=f"[`{sha[:7]}`]({_susfs_url}/-/commit/{sha})" if sha else "_n/a_"
  1196. _row+=f" {sha_s} |"
  1197. lines.append(_row)
  1198. lines.append("")
  1199. lines.append("")
  1200. output="\n".join(lines)+"\n"
  1201. print(output,end="")
  1202. s=os.environ.get("GITHUB_STEP_SUMMARY")
  1203. if s: open(s,"a").write(output)
  1204. PYEOF
  1205. - name: Upload release notes artifact
  1206. uses: actions/upload-artifact@v7
  1207. with:
  1208. name: release-notes
  1209. path: release_body.md
  1210. retention-days: 7
  1211. if-no-files-found: error
  1212. - name: Download Manager APK zips
  1213. uses: actions/download-artifact@v7
  1214. with:
  1215. path: release-assets/manager
  1216. pattern: '*.apk'
  1217. merge-multiple: true
  1218. if-no-files-found: ignore
  1219. - name: Download AnyKernel3 Artifacts
  1220. uses: actions/download-artifact@v7
  1221. with:
  1222. path: release-assets
  1223. pattern: '*-AnyKernel3'
  1224. merge-multiple: false
  1225. - name: Download NoMount metamodule
  1226. uses: actions/download-artifact@v7
  1227. with:
  1228. path: release-assets
  1229. pattern: '*NoMount-Metamodule'
  1230. merge-multiple: false
  1231. if-no-files-found: ignore
  1232. - name: Zip AnyKernel3 for Release
  1233. run: |
  1234. set -e
  1235. shopt -s nullglob
  1236. for dir in release-assets/*-AnyKernel3; do
  1237. zip_name="${dir}.zip"
  1238. rm -f "$zip_name"
  1239. cd "$dir"
  1240. zip -r -q -9 "../$(basename "$zip_name")" .
  1241. cd - >/dev/null
  1242. done
  1243. - name: Zip NoMount Metamodule for Release
  1244. run: |
  1245. set -e
  1246. shopt -s nullglob
  1247. for dir in release-assets/*NoMount-Metamodule; do
  1248. zip_name="${dir}.zip"
  1249. rm -f "$zip_name"
  1250. cd "$dir"
  1251. zip -r -q -9 "../$(basename "$zip_name")" .
  1252. cd - >/dev/null
  1253. done
  1254. - name: Create Release
  1255. uses: softprops/action-gh-release@v1
  1256. with:
  1257. tag_name: ${{ steps.tag.outputs.new_tag }}
  1258. name: ${{ steps.release_meta.outputs.release_title }}
  1259. body_path: release_body.md
  1260. draft: false
  1261. prerelease: ${{ inputs.release_type == 'Pre-Release' }}
  1262. files: |
  1263. release-assets/**/*.zip
  1264. release-assets/**/*.apk
  1265. release-assets/**/*-boot*.img
  1266. nightly-release:
  1267. if: always() && github.event_name == 'workflow_dispatch' && !inputs.test_release_notes && inputs.release_type == 'Action'
  1268. needs: [notes-preview]
  1269. runs-on: ubuntu-latest
  1270. concurrency:
  1271. group: nightly-release-${{ github.ref }}
  1272. cancel-in-progress: false
  1273. steps:
  1274. - uses: actions/checkout@v7
  1275. - name: Download release notes preview
  1276. uses: actions/download-artifact@v7
  1277. with:
  1278. name: release-notes-preview
  1279. path: nightly
  1280. if-no-files-found: error
  1281. - name: Append nightly.link URL
  1282. run: |
  1283. {
  1284. echo ""
  1285. echo "- **Nightly artifacts:** https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}"
  1286. echo "- **Note:** not every kernel version is necessarily built in a nightly run — the link above lists exactly what this run produced."
  1287. } >> nightly/release_body.md
  1288. cat nightly/release_body.md >> "$GITHUB_STEP_SUMMARY"
  1289. - name: Replace nightly prerelease
  1290. env:
  1291. GH_TOKEN: ${{ github.token }}
  1292. run: |
  1293. set -e
  1294. gh release delete nightly --cleanup-tag -y || true
  1295. if git ls-remote --tags origin nightly | grep -q 'refs/tags/nightly'; then
  1296. git push --delete origin nightly || true
  1297. fi
  1298. gh release create nightly \
  1299. --target "${{ github.sha }}" \
  1300. --prerelease \
  1301. --title "Nightly (${{ github.ref_name }} $(date -u +%Y-%m-%d))" \
  1302. --notes-file nightly/release_body.md
  1303. notes-preview:
  1304. if: always() && github.event_name == 'workflow_dispatch'
  1305. needs: [resolve-sources, build-android12-5-10, build-android13-5-10, build-android13-5-15, build-android14-5-15, build-android14-6-1, build-android15-6-6, build-android16-6-12, release]
  1306. runs-on: ubuntu-latest
  1307. steps:
  1308. - uses: actions/checkout@v7
  1309. - name: Render release notes preview (always)
  1310. env:
  1311. ROOT_FLAVOR: ${{ inputs.root_flavor }}
  1312. FEATURE_SET: ${{ needs.resolve-sources.outputs.feature_set }}
  1313. MANAGER_LIST: ${{ needs.resolve-sources.outputs.manager_list }}
  1314. ROOT_MATRIX: ${{ needs.resolve-sources.outputs.root_matrix }}
  1315. USE_SUSFS: ${{ inputs.use_susfs }}
  1316. USE_NOMOUNT: ${{ inputs.use_nomount }}
  1317. USE_BBG: ${{ inputs.use_bbg }}
  1318. USE_DS: ${{ inputs.use_ds }}
  1319. USE_NET: ${{ inputs.use_net }}
  1320. USE_NTSYNC: ${{ inputs.use_ntsync }}
  1321. USE_PTRACE: ${{ inputs.use_ptrace }}
  1322. USE_UNICODE: ${{ inputs.use_unicode }}
  1323. USE_BPF: ${{ inputs.use_bpf }}
  1324. USE_PERF: ${{ inputs.use_perf }}
  1325. USE_CACHE: ${{ inputs.use_cache }}
  1326. KERNEL_BUILD_VERSION: ${{ inputs.kernel_build_version }}
  1327. NOMOUNT_COMMIT: ${{ needs.resolve-sources.outputs.nomount_commit }}
  1328. KSU_COMMIT: ${{ needs.resolve-sources.outputs.ksu_commit }}
  1329. RESUKISU_COMMIT: ${{ needs.resolve-sources.outputs.resukisu_commit }}
  1330. KERNELSU_COMMIT: ${{ needs.resolve-sources.outputs.kernelsu_commit }}
  1331. KERNEL_PATCHES_COMMIT: ${{ needs.resolve-sources.outputs.kernel_patches_commit }}
  1332. ANYKERNEL3_COMMIT: ${{ needs.resolve-sources.outputs.anykernel3_commit }}
  1333. DROIDSPACES_COMMIT: ${{ needs.resolve-sources.outputs.droidspaces_commit }}
  1334. SUSFS_ANDROID12_5_10: ${{ needs.resolve-sources.outputs.susfs_commit_android12_5_10 }}
  1335. SUSFS_ANDROID13_5_10: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_10 }}
  1336. SUSFS_ANDROID13_5_15: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_15 }}
  1337. SUSFS_ANDROID14_5_15: ${{ needs.resolve-sources.outputs.susfs_commit_android14_5_15 }}
  1338. SUSFS_ANDROID14_6_1: ${{ needs.resolve-sources.outputs.susfs_commit_android14_6_1 }}
  1339. SUSFS_ANDROID15_6_6: ${{ needs.resolve-sources.outputs.susfs_commit_android15_6_6 }}
  1340. SUSFS_ANDROID16_6_12: ${{ needs.resolve-sources.outputs.susfs_commit_android16_6_12 }}
  1341. SUSFS_ANDROID12_5_10_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android12_5_10_resukisu }}
  1342. SUSFS_ANDROID13_5_10_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_10_resukisu }}
  1343. SUSFS_ANDROID13_5_15_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_15_resukisu }}
  1344. SUSFS_ANDROID14_5_15_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android14_5_15_resukisu }}
  1345. SUSFS_ANDROID14_6_1_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android14_6_1_resukisu }}
  1346. SUSFS_ANDROID15_6_6_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android15_6_6_resukisu }}
  1347. SUSFS_ANDROID16_6_12_RESUKISU: ${{ needs.resolve-sources.outputs.susfs_commit_android16_6_12_resukisu }}
  1348. # KSU version not computed here; render will show placeholder if release job skipped
  1349. run: |
  1350. set -e
  1351. python3 - <<'PYEOF' > release_body.md
  1352. import json, os
  1353. def env(n,d=""): return os.environ.get(n,d).strip() or d
  1354. def parse_managers(raw):
  1355. try: return json.loads(raw) if raw.strip() else []
  1356. except: return []
  1357. root = env("ROOT_FLAVOR","All")
  1358. feats=[]
  1359. if env("USE_SUSFS","true")=="true": feats.append("SUSFS")
  1360. if env("USE_NOMOUNT","true")=="true": feats.append("NoMount")
  1361. if env("USE_BBG","true")=="true": feats.append("Baseband Guard")
  1362. if env("USE_DS","true")=="true": feats.append("DroidSpaces-OSS")
  1363. if env("USE_NET","true")=="true": feats.append("Networking")
  1364. if env("USE_NTSYNC","true")=="true": feats.append("NTSync")
  1365. if env("USE_PTRACE","true")=="true": feats.append("Ptrace Leak Fix")
  1366. if env("USE_UNICODE","true")=="true": feats.append("Unicode Fix")
  1367. if env("USE_BPF","true")=="true": feats.append("BTF / eBPF / FUSE-BPF")
  1368. if env("USE_PERF","false")=="true": feats.append("Performance Tuning")
  1369. lines=[]
  1370. lines.append("# Wild Kernels for GKI2 Devices")
  1371. lines.append("")
  1372. lines.append("## Root")
  1373. lines.append("")
  1374. ksun_branch=env("KSUN_BRANCH","dev-susfs")
  1375. ksun_commit=env("KSUN_COMMIT","")
  1376. ksu_commit=env("KSU_COMMIT","")
  1377. resukisu_commit=env("RESUKISU_COMMIT","")
  1378. kernelsu_commit=env("KERNELSU_COMMIT","")
  1379. nm=env("NOMOUNT_COMMIT","")
  1380. kp=env("KERNEL_PATCHES_COMMIT","")
  1381. ak=env("ANYKERNEL3_COMMIT","")
  1382. ds=env("DROIDSPACES_COMMIT","")
  1383. _rmatrix={}
  1384. try: _rmatrix={m.get("root_flavor",""):m.get("root_commit","") for m in json.loads(env("ROOT_MATRIX","[]")) if isinstance(m,dict)}
  1385. except: pass
  1386. if env("USE_SUSFS","true")=="true":
  1387. _next_url="https://github.com/pershoot/KernelSU-Next/commit/"
  1388. else:
  1389. _next_url="https://github.com/KernelSU-Next/KernelSU-Next/commit/"
  1390. _root_urls={"next":_next_url,"kernelsu":"https://github.com/tiann/KernelSU/commit/","resukisu":"https://github.com/ReSukiSU/ReSukiSU/commit/"}
  1391. _root_commits={"next":_rmatrix.get("next","") or kernelsu_commit,"kernelsu":_rmatrix.get("kernelsu","") or ksu_commit,"resukisu":_rmatrix.get("resukisu","") or resukisu_commit}
  1392. _flavors0=[m.get("flavor","") for m in parse_managers(env("MANAGER_LIST","")) if m.get("flavor","")]
  1393. if not _flavors0:
  1394. _flavors0={"KernelSU-Next":["next"],"KernelSU":["kernelsu"],"ReSukiSU":["resukisu"],"All":["next","kernelsu","resukisu"]}.get(root,["next","kernelsu","resukisu"])
  1395. _labels0={"next":"KernelSU-Next","kernelsu":"KernelSU","resukisu":"ReSukiSU"}
  1396. _rc=[]
  1397. for _fl0 in _flavors0:
  1398. _c=_root_commits.get(_fl0,"")
  1399. if _c and len(_c)==40: _rc.append(f"{_labels0.get(_fl0,_fl0)} [`{_c[:7]}`]({_root_urls[_fl0]}{_c})")
  1400. elif _c: _rc.append(f"{_labels0.get(_fl0,_fl0)} `{_c}`")
  1401. for _r in _rc: lines.append(f"- {_r}")
  1402. lines.append("")
  1403. lines.append("## Features")
  1404. lines.append("")
  1405. _kp_url="https://github.com/WildKernels/kernel_patches/commit/"
  1406. _feat_commit={"NoMount":("https://github.com/maxsteeel/nomount/commit/",nm),"Baseband Guard":(_kp_url,kp),"DroidSpaces-OSS":("https://github.com/ravindu644/Droidspaces-OSS/commit/",ds)}
  1407. for f in feats:
  1408. _u,_c=_feat_commit.get(f,("",""))
  1409. if _c and len(_c)==40: lines.append(f"- {f} [`{_c[:7]}`]({_u}{_c})")
  1410. elif _c: lines.append(f"- {f} `{_c}`")
  1411. else: lines.append(f"- {f}")
  1412. if not feats: lines.append("- (none)")
  1413. kp=env("KERNEL_PATCHES_COMMIT","")
  1414. ak=env("ANYKERNEL3_COMMIT","")
  1415. ds=env("DROIDSPACES_COMMIT","")
  1416. lines.append("")
  1417. lines.append("## SUSFS commits")
  1418. lines.append("")
  1419. all_variants=["android12-5.10","android13-5.10","android13-5.15","android14-5.15","android14-6.1","android15-6.6","android16-6.12"]
  1420. def kbv_to_variant(kbv):
  1421. if ".x-android" in kbv:
  1422. kv, av = kbv.split(".x-android", 1)
  1423. v = f"android{av}-{kv}"
  1424. return v if v in all_variants else ""
  1425. return ""
  1426. kbv=env("KERNEL_BUILD_VERSION","All")
  1427. _sel=kbv_to_variant(kbv)
  1428. variants=[_sel] if _sel else list(all_variants)
  1429. sus_map={"android12-5.10":env("SUSFS_ANDROID12_5_10",""),"android13-5.10":env("SUSFS_ANDROID13_5_10",""),"android13-5.15":env("SUSFS_ANDROID13_5_15",""),"android14-5.15":env("SUSFS_ANDROID14_5_15",""),"android14-6.1":env("SUSFS_ANDROID14_6_1",""),"android15-6.6":env("SUSFS_ANDROID15_6_6",""),"android16-6.12":env("SUSFS_ANDROID16_6_12","")}
  1430. sus_map_resukisu={"android12-5.10":env("SUSFS_ANDROID12_5_10_RESUKISU",""),"android13-5.10":env("SUSFS_ANDROID13_5_10_RESUKISU",""),"android13-5.15":env("SUSFS_ANDROID13_5_15_RESUKISU",""),"android14-5.15":env("SUSFS_ANDROID14_5_15_RESUKISU",""),"android14-6.1":env("SUSFS_ANDROID14_6_1_RESUKISU",""),"android15-6.6":env("SUSFS_ANDROID15_6_6_RESUKISU",""),"android16-6.12":env("SUSFS_ANDROID16_6_12_RESUKISU","")}
  1431. _flavors=[m.get("flavor","") for m in parse_managers(env("MANAGER_LIST","")) if m.get("flavor","")]
  1432. if not _flavors:
  1433. _flavors={"KernelSU-Next":["next"],"KernelSU":["kernelsu"],"ReSukiSU":["resukisu"],"All":["next","kernelsu","resukisu"]}.get(root,["next","kernelsu","resukisu"])
  1434. _labels={"next":"KernelSU-Next","kernelsu":"KernelSU","resukisu":"ReSukiSU"}
  1435. _susfs_url="https://gitlab.com/simonpunk/susfs4ksu"
  1436. lines.append("| SUSFS branch |" + "".join(f" {_labels.get(_fl,_fl)} |" for _fl in _flavors))
  1437. lines.append("|---" * (len(_flavors) + 1) + "|")
  1438. for v in variants:
  1439. _row=f"| gki-{v} |"
  1440. for _fl in _flavors:
  1441. _sm=sus_map_resukisu if _fl=="resukisu" else sus_map
  1442. sha=_sm.get(v,"")
  1443. sha_s=f"[`{sha[:7]}`]({_susfs_url}/-/commit/{sha})" if sha else "_n/a_"
  1444. _row+=f" {sha_s} |"
  1445. lines.append(_row)
  1446. lines.append("")
  1447. lines.append("")
  1448. output="\n".join(lines)+"\n"
  1449. print(output,end="")
  1450. s=os.environ.get("GITHUB_STEP_SUMMARY")
  1451. if s: open(s,"a").write(output)
  1452. PYEOF
  1453. - name: Upload release notes preview artifact
  1454. uses: actions/upload-artifact@v7
  1455. with:
  1456. name: release-notes-preview
  1457. path: release_body.md
  1458. retention-days: 7
  1459. if-no-files-found: warn