render_release_body.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import json
  2. import os
  3. import sys
  4. from pathlib import Path
  5. PLACEHOLDERS = {
  6. "{{KSU_VERSION}}": lambda: os.environ.get("KSU_VERSION", "unknown"),
  7. "{{KSU_GIT_TAG}}": lambda: os.environ.get("KSU_GIT_TAG", "no-tag"),
  8. "{{KSUN_BRANCH}}": lambda: os.environ.get("KSUN_BRANCH", "dev"),
  9. "{{KSUN_COMMIT}}": lambda: os.environ.get("KSUN_COMMIT", "unknown"),
  10. "{{KSU_MANAGER}}": lambda: os.environ.get("KSU_MANAGER", "Placeholder"),
  11. "{{KSU_MANAGER_NOTE}}": lambda: os.environ.get("KSU_MANAGER_NOTE", ""),
  12. "{{SUSFS_BRANCHES}}": lambda: os.environ.get("SUSFS_COMMIT", "latest on auto-derived gki-{version} branch"),
  13. "{{SUSFS_BRANCHS}}": lambda: os.environ.get("SUSFS_COMMIT", "latest on auto-derived gki-{version} branch"),
  14. }
  15. def build_managers_markdown(text: str) -> str:
  16. raw = os.environ.get("MANAGER_LIST", "").strip()
  17. if not raw:
  18. return text
  19. try:
  20. managers = json.loads(raw)
  21. except Exception:
  22. return text
  23. if not isinstance(managers, list) or not managers:
  24. return text
  25. # Build markdown list for all selected managers
  26. flavor_labels = {"next": "KernelSU-Next", "kernelsu": "KernelSU", "resukisu": "ReSukiSU"}
  27. lines = []
  28. for m in managers:
  29. flavor = m.get("flavor", "")
  30. label = flavor_labels.get(flavor, flavor or "unknown")
  31. run_id = m.get("run_id")
  32. owner = m.get("owner", "")
  33. repo = m.get("repo", "")
  34. stock = m.get("stock", "")
  35. if run_id and owner and repo:
  36. url = f"https://github.com/{owner}/{repo}/actions/runs/{run_id}"
  37. note = f" — stock `{stock[:12]}`" if stock else ""
  38. lines.append(f"- **{label}:** [build-manager run]({url}){note}")
  39. else:
  40. lines.append(f"- **{label}** manager (no run ID)")
  41. replacement = "**Managers:**\n" + "\n".join(lines)
  42. # Replace the single-manager line in template
  43. # Template has: **Manager:** [build-manager run]({{KSU_MANAGER}}) — {{KSU_MANAGER_NOTE}}
  44. # Replace that whole line if present
  45. import re
  46. text = re.sub(r"\*\*Manager:\*\*.*\n", replacement + "\n", text, count=1)
  47. return text
  48. def filter_sections(text: str) -> str:
  49. # Map heading substring -> required env flag
  50. # If flag is "false", that section is dropped.
  51. flag_map = {
  52. "SUSFS": os.environ.get("USE_SUSFS", "true") == "true",
  53. "Baseband Guard": os.environ.get("USE_BBG", "true") == "true",
  54. "BBG": os.environ.get("USE_BBG", "true") == "true",
  55. "DroidSpaces": os.environ.get("USE_DS", "true") == "true",
  56. "Networking": os.environ.get("USE_NET", "true") == "true",
  57. "NTSync": os.environ.get("USE_NTSYNC", "true") == "true",
  58. "Ptrace": os.environ.get("USE_PTRACE", "true") == "true",
  59. "Unicode": os.environ.get("USE_UNICODE", "true") == "true",
  60. "BPF": os.environ.get("USE_BPF", "true") == "true",
  61. }
  62. # Split keeping delimiters: first chunk is preamble before first ##
  63. parts = text.split("\n## ")
  64. if len(parts) <= 1:
  65. return text
  66. kept = [parts[0]]
  67. for part in parts[1:]:
  68. heading_line = part.split("\n", 1)[0]
  69. keep = True
  70. for key, enabled in flag_map.items():
  71. if key.lower() in heading_line.lower() and not enabled:
  72. keep = False
  73. break
  74. # Also drop specific subsections inside Misc/Other Features if relevant
  75. # For now only top-level headings filtered; keep is per heading.
  76. if keep:
  77. kept.append("## " + part)
  78. return "\n".join(kept) if len(kept) > 1 else parts[0] + "\n## ".join(kept[1:])
  79. def inject_feature_summary(text: str) -> str:
  80. feature_set = os.environ.get("FEATURE_SET", "").strip()
  81. root_flavor = os.environ.get("ROOT_FLAVOR", "").strip()
  82. if not feature_set and not root_flavor:
  83. return text
  84. summary_lines = []
  85. if root_flavor:
  86. summary_lines.append(f"**Root:** {root_flavor}")
  87. if feature_set:
  88. summary_lines.append(f"**Features:** {feature_set}")
  89. # Insert after the Features anchor list or after disclaimer
  90. summary = "> " + " | ".join(summary_lines) + "\n" if summary_lines else ""
  91. # Find the Features anchor list end (line with <!-- NOTE:)
  92. marker = "<!-- NOTE:"
  93. if marker in text and summary:
  94. text = text.replace(marker, summary + "\n" + marker, 1)
  95. return text
  96. def render_markdown(template_path: Path):
  97. text = template_path.read_text()
  98. for placeholder, getter in PLACEHOLDERS.items():
  99. text = text.replace(placeholder, getter())
  100. text = build_managers_markdown(text)
  101. text = inject_feature_summary(text)
  102. text = filter_sections(text)
  103. print(text, end="")
  104. config_path = Path(sys.argv[1])
  105. if config_path.suffix.lower() == ".md":
  106. render_markdown(config_path)
  107. sys.exit(0)
  108. # Backward-compatible JSON renderer for older release configs.
  109. def emit(text=""):
  110. print(text)
  111. def emit_list(items):
  112. if isinstance(items, list):
  113. for item in items:
  114. emit(f"- {item}")
  115. def emit_description(value):
  116. if isinstance(value, list):
  117. for line in value:
  118. emit(line)
  119. elif value:
  120. emit(str(value))
  121. data = json.loads(config_path.read_text())
  122. emit("**IMPORTANT DISCLAIMER**")
  123. for line in data["release"]["disclaimer"]:
  124. emit(line)
  125. kernelsu = data.get("kernelsu", {})
  126. emit()
  127. emit(f"## {kernelsu.get('name', 'KernelSU-Next')}")
  128. emit(f"- Version: {os.environ.get('KSU_VERSION', kernelsu.get('version', 'unknown'))}")
  129. emit(f"- Tag: {os.environ.get('KSU_GIT_TAG', kernelsu.get('tag', 'no-tag'))}")
  130. emit(f"- Branch: {os.environ.get('KSUN_BRANCH', kernelsu.get('branch', 'dev'))}")
  131. emit(f"- Commit: {os.environ.get('KSUN_COMMIT', kernelsu.get('commit', 'unknown'))}")
  132. if kernelsu.get("url"):
  133. emit(f"- URL: {kernelsu['url']}")
  134. if kernelsu.get("manager"):
  135. emit(f"- Manager: {kernelsu['manager']}")
  136. skip_keys = {"release", "kernelsu"}
  137. for key in data.keys():
  138. if key in skip_keys:
  139. continue
  140. section = data[key]
  141. emit()
  142. emit(f"## {section.get('name', key)}")
  143. if section.get("description"):
  144. emit_description(section["description"])
  145. if section.get("version"):
  146. emit(f"- Version: {section['version']}")
  147. if section.get("tag"):
  148. emit(f"- Tag: {section['tag']}")
  149. if section.get("branch"):
  150. emit(f"- Branch: {section['branch']}")
  151. if key == "susfs":
  152. susfs_commit = os.environ.get("SUSFS_COMMIT", "")
  153. if susfs_commit:
  154. emit(f"- Commit: `{susfs_commit}`")
  155. else:
  156. emit("- Commit: latest on auto-derived gki-{version} branch")
  157. if section.get("items"):
  158. emit_list(section["items"])
  159. if section.get("url"):
  160. emit(f"- URL: {section['url']}")