render_release_body.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/bin/env python3
  2. """
  3. Render release notes dynamically from workflow environment variables.
  4. No static template file is read. The script assembles the markdown directly
  5. from env vars set by the workflow.
  6. Invoke without a template file argument:
  7. python3 .github/scripts/render_release_body.py > release_body.md
  8. """
  9. import json
  10. import os
  11. from pathlib import Path
  12. ROOT = Path(os.environ.get("GITHUB_WORKSPACE", "."))
  13. def env(name: str, default: str = "") -> str:
  14. return os.environ.get(name, default).strip()
  15. def parse_managers(raw: str):
  16. raw = raw.strip()
  17. if not raw:
  18. return []
  19. try:
  20. data = json.loads(raw)
  21. except json.JSONDecodeError:
  22. return []
  23. if not isinstance(data, list):
  24. return []
  25. return data
  26. def build_preamble() -> str:
  27. return (
  28. "# Wild Kernels for GKI2 Devices\n\n"
  29. "> [!CAUTION]\n"
  30. "> This software is provided for testing and educational purposes only. "
  31. "Use at your own risk. The developers are not responsible for any damage, "
  32. "data loss, or issues that may occur. Please ensure you have proper backups "
  33. "before installation.\n\n"
  34. "Join the Telegram group: <https://t.me/WildKernelsTG>\n\n"
  35. "---\n\n"
  36. )
  37. def build_managers_section(managers) -> str:
  38. if not managers:
  39. return ""
  40. flavor_labels = {"next": "KernelSU-Next", "kernelsu": "KernelSU", "resukisu": "ReSukiSU"}
  41. lines = []
  42. for m in managers:
  43. flavor = m.get("flavor", "")
  44. label = flavor_labels.get(flavor, flavor or "unknown")
  45. run_id = m.get("run_id")
  46. stock = m.get("stock", "")
  47. if run_id:
  48. url = f"https://github.com/{m.get('owner', '')}/{m.get('repo', '')}/actions/runs/{run_id}"
  49. stock_note = f" (stock `{stock[:12]}`)" if stock else ""
  50. lines.append(f"- **{label}:** [{url}]({url}){stock_note}")
  51. else:
  52. lines.append(f"- **{label}:** release assets")
  53. return "**Managers:**\n" + "\n".join(lines) + "\n"
  54. def build_ksu_section() -> str:
  55. version = env("KSU_VERSION", "unknown")
  56. tag = env("KSU_GIT_TAG", "no-tag")
  57. branch = env("KSUN_BRANCH", "dev")
  58. commit = env("KSUN_COMMIT", "unknown")
  59. manager_url = env("KSU_MANAGER", "")
  60. manager_note = env("KSU_MANAGER_NOTE", "")
  61. parts = ["## KernelSU", ""]
  62. parts.append(f"- **Version:** `{version}`")
  63. if tag and tag != "no-tag":
  64. parts.append(f"- **Tag:** `{tag}`")
  65. parts.append(f"- **Branch:** `{branch}`")
  66. parts.append(f"- **Commit:** `{commit}`")
  67. if manager_url:
  68. note = f" — {manager_note}" if manager_note else ""
  69. base = manager_url.split("/actions/")[0]
  70. parts.append(f"- **Manager:** [{base}]({manager_url}){note}")
  71. parts.append("")
  72. return "\n".join(parts)
  73. def build_susfs_section() -> str:
  74. variant_envs = [
  75. ("android12-5.10", "susfs_commit_android12_5_10"),
  76. ("android13-5.10", "susfs_commit_android13_5_10"),
  77. ("android13-5.15", "susfs_commit_android13_5_15"),
  78. ("android14-5.15", "susfs_commit_android14_5_15"),
  79. ("android14-6.1", "susfs_commit_android14_6_1"),
  80. ("android15-6.6", "susfs_commit_android15_6_6"),
  81. ("android16-6.12", "susfs_commit_android16_6_12"),
  82. ]
  83. per_variant = []
  84. for variant, env_key in variant_envs:
  85. sha = env(env_key)
  86. if sha and len(sha) == 40:
  87. per_variant.append((variant, sha))
  88. if per_variant:
  89. parts = [
  90. "## SUSFS",
  91. "",
  92. "Pinned SUSFS commits per Android/kernel variant:",
  93. "",
  94. ]
  95. for variant, sha in per_variant:
  96. parts.append(f"- **{variant}:** `{sha}`")
  97. parts.append("")
  98. return "\n".join(parts)
  99. sha = env("SUSFS_COMMIT", "")
  100. if sha:
  101. return f"## SUSFS\n\n- **Commit:** `{sha}`\n"
  102. return ""
  103. def build_root_section() -> str:
  104. root_flavor = env("ROOT_FLAVOR", "unknown")
  105. feature_set = env("FEATURE_SET", "")
  106. parts = ["## This Build", ""]
  107. parts.append(f"- **Root:** `{root_flavor}`")
  108. if feature_set:
  109. parts.append(f"- **Feature Set:** `{feature_set}`")
  110. parts.append("")
  111. return "\n".join(parts)
  112. def build_features_section() -> str:
  113. enabled = []
  114. if env("USE_SUSFS", "true") == "true":
  115. enabled.append("SUSFS")
  116. if env("USE_BBG", "true") == "true":
  117. enabled.append("Baseband Guard")
  118. if env("USE_DS", "true") == "true":
  119. enabled.append("DroidSpaces-OSS")
  120. if env("USE_NET", "true") == "true":
  121. enabled.append("Networking")
  122. if env("USE_NTSYNC", "true") == "true":
  123. enabled.append("NTSync")
  124. if env("USE_PTRACE", "true") == "true":
  125. enabled.append("Ptrace Leak Fix")
  126. if env("USE_UNICODE", "true") == "true":
  127. enabled.append("Unicode Fix")
  128. if env("USE_BPF", "true") == "true":
  129. enabled.append("BTF / eBPF / FUSE-BPF")
  130. if env("USE_PERF", "true") == "true":
  131. enabled.append("Performance Tuning")
  132. if not enabled:
  133. return ""
  134. parts = [
  135. "## Features Included",
  136. "",
  137. "Each feature is documented separately in `docs/`:",
  138. "",
  139. ]
  140. feature_doc_map = [
  141. ("SUSFS", "susfs.md"),
  142. ("Baseband Guard", "bbg.md"),
  143. ("DroidSpaces-OSS", "droidspaces.md"),
  144. ("Networking", "networking.md"),
  145. ("NTSync", "ntsync.md"),
  146. ("Ptrace Leak Fix", "ptrace.md"),
  147. ("Unicode Fix", "unicode.md"),
  148. ("BTF / eBPF / FUSE-BPF", "bpf.md"),
  149. ("Performance Tuning", "performance.md"),
  150. ]
  151. for feature, doc in feature_doc_map:
  152. if feature in enabled:
  153. parts.append(f"- [{feature}](docs/{doc})")
  154. parts.append("")
  155. return "\n".join(parts)
  156. def render() -> str:
  157. parts = []
  158. parts.append(build_preamble())
  159. parts.append(build_root_section())
  160. parts.append(build_ksu_section())
  161. parts.append(build_susfs_section())
  162. parts.append(build_managers_section(parse_managers(env("MANAGER_LIST", ""))))
  163. parts.append(build_features_section())
  164. return "\n".join(parts)
  165. if __name__ == "__main__":
  166. output = render()
  167. print(output, end="")
  168. step_summary = os.environ.get("GITHUB_STEP_SUMMARY")
  169. if step_summary:
  170. Path(step_summary).write_text(output)