Przeglądaj źródła

feat(main): add commit_mode 'update' to promote verified pins on success

Add a third commit_mode option 'update': builds on latest tips, then
only if all enabled build jobs pass, promotes the built SHAs into the
verified PIN_* block. Only components enabled this run are promoted
(enabled root flavors, NoMount, and the SUSFS branches for built kernels).
Previous verified pins are archived to .github/pins/history/<ts>.json
before being replaced. resolve-sources now also emits classic_commit and
resukisu_commit so the promotion job can read them.
TheWildJames 2 tygodni temu
rodzic
commit
45e223185c
2 zmienionych plików z 200 dodań i 1 usunięć
  1. 129 0
      .github/scripts/update_verified_pins.py
  2. 71 1
      .github/workflows/main.yml

+ 129 - 0
.github/scripts/update_verified_pins.py

@@ -0,0 +1,129 @@
+#!/usr/bin/env python3
+"""Promote latest-built commits to the verified PIN_* block in main.yml.
+
+Update mode: builds happen at latest tips. After the build, if the components
+built this run all passed, their used SHAs overwrite the audited pins. Previous
+audited pins are recorded in .github/pins/history/<date>.json before being
+replaced. Only components that were enabled AND passed are promoted; unrelated
+pins are left untouched.
+"""
+import json
+import os
+import re
+import sys
+from datetime import datetime, timezone
+
+REPO = os.environ.get("GITHUB_WORKSPACE", ".")
+MAIN = os.path.join(REPO, ".github/workflows/main.yml")
+
+# (pin-var, resolved-sha-env, approval-env)
+# approval env is "1"/"true" when the component was enabled and all its builds passed.
+PINS = [
+    ("PIN_NOMOUNT", "NOMOUNT_SHA", "APPROVE_NOMOUNT"),
+    ("PIN_CLASSIC", "CLASSIC_SHA", "APPROVE_CLASSIC"),
+    ("PIN_RESUKISU", "RESUKISU_SHA", "APPROVE_RESUKISU"),
+    ("PIN_SUSFS[susfs_commit_android12_5_10]", "SUSFS_ANDROID12_5_10_SHA", "APPROVE_SUSFS_ANDROID12_5_10"),
+    ("PIN_SUSFS[susfs_commit_android13_5_10]", "SUSFS_ANDROID13_5_10_SHA", "APPROVE_SUSFS_ANDROID13_5_10"),
+    ("PIN_SUSFS[susfs_commit_android13_5_15]", "SUSFS_ANDROID13_5_15_SHA", "APPROVE_SUSFS_ANDROID13_5_15"),
+    ("PIN_SUSFS[susfs_commit_android14_5_15]", "SUSFS_ANDROID14_5_15_SHA", "APPROVE_SUSFS_ANDROID14_5_15"),
+    ("PIN_SUSFS[susfs_commit_android14_6_1]", "SUSFS_ANDROID14_6_1_SHA", "APPROVE_SUSFS_ANDROID14_6_1"),
+    ("PIN_SUSFS[susfs_commit_android15_6_6]", "SUSFS_ANDROID15_6_6_SHA", "APPROVE_SUSFS_ANDROID15_6_6"),
+    ("PIN_SUSFS[susfs_commit_android16_6_12]", "SUSFS_ANDROID16_6_12_SHA", "APPROVE_SUSFS_ANDROID16_6_12"),
+]
+
+SHA_RE = re.compile(r"^[0-9a-f]{40}$")
+
+
+def approved(env):
+    return os.environ.get(env, "").strip() in ("1", "true", "True")
+
+
+def read_pins():
+    """Return dict {var_name: sha} from the current PIN_* block in main.yml."""
+    with open(MAIN, "r", encoding="utf-8") as fh:
+        text = fh.read()
+    pins = {}
+    for var, _, _ in PINS:
+        if var.startswith("PIN_SUSFS["):
+            key = var[len("PIN_SUSFS["):-1]
+            # match:  [susfs_commit_android12_5_10]="<sha>"
+            m = re.search(r"\[%s\]=\"([0-9a-f]{40})\"" % re.escape(key), text)
+            pins[key] = m.group(1) if m else None
+        else:
+            m = re.search(r"%s=\"([0-9a-f]{40})\"" % re.escape(var), text)
+            pins[var] = m.group(1) if m else None
+    return pins
+
+
+def build_changes():
+    changes = []          # (var, old_sha, new_sha)
+    promoted = {}         # key -> sha for pins being set
+    for var, sha_env, approve_env in PINS:
+        new_sha = os.environ.get(sha_env, "").strip().lower()
+        if not SHA_RE.match(new_sha):
+            print(f"  skip {var}: resolved SHA invalid ({new_sha})", file=sys.stderr)
+            continue
+        if not approved(approve_env):
+            print(f"  skip {var}: not approved to promote (disabled or a build failed)", file=sys.stderr)
+            continue
+        key = var[len("PIN_SUSFS["):-1] if var.startswith("PIN_SUSFS[") else var
+        old_sha = read_pins().get(key)
+        if old_sha == new_sha:
+            print(f"  unchanged {key} ({new_sha[:8]})")
+            continue
+        promoted[key] = new_sha
+        changes.append((key, old_sha, new_sha))
+    return changes, promoted
+
+
+def history_path():
+    ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H%M%SZ")
+    d = os.path.join(REPO, ".github/pins/history")
+    os.makedirs(d, exist_ok=True)
+    return os.path.join(d, f"{ts}.json")
+
+
+def write_history(changes, path):
+    record = {
+        "updated_at": datetime.now(timezone.utc).isoformat(),
+        "previous_verified": {key: (old or "") for key, old, _ in changes},
+    }
+    with open(path, "w", encoding="utf-8") as fh:
+        json.dump(record, fh, indent=2)
+        fh.write("\n")
+    print(f"  history written: {os.path.relpath(path, REPO)}")
+
+
+def apply_pins(promoted):
+    with open(MAIN, "r", encoding="utf-8") as fh:
+        text = fh.read()
+    for key, sha in promoted.items():
+        pattern = re.compile(r"\[%s\]=\"([0-9a-f]{40})\"" % re.escape(key) if not key.startswith("PIN_")
+                            else r"%s=\"([0-9a-f]{40})\"" % re.escape(key))
+        text, n = pattern.subn(lambda m: (f"[{key}]=\"{sha}\"" if not key.startswith("PIN_")
+                                          else f"{key}=\"{sha}\""), text, count=1)
+        if n == 0:
+            print(f"  ERROR: could not locate pin for {key}", file=sys.stderr)
+            return False
+    with open(MAIN, "w", encoding="utf-8") as fh:
+        fh.write(text)
+    return True
+
+
+def main():
+    changes, promoted = build_changes()
+    if not changes:
+        print("No pins to promote (nothing changed or nothing approved).")
+        return 0
+    hist = history_path()
+    write_history(changes, hist)
+    if not apply_pins(promoted):
+        return 1
+    print("Promoted pins:")
+    for key, old, new in changes:
+        print(f"  {key}: {old[:8] if old else 'none'} -> {new[:8]}")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 71 - 1
.github/workflows/main.yml

@@ -38,12 +38,13 @@ on:
         type: string
         default: Wild
       commit_mode:
-        description: "verified = audited pinned SHAs (pending), latest = branch tips at run time"
+        description: "latest = branch tips, verified = audited pins, update = build latest then promote verified pins on success"
         required: false
         type: choice
         options:
           - latest
           - verified
+          - update
         default: latest
       root_flavor:
         description: "Root Flavor"
@@ -146,6 +147,8 @@ jobs:
       susfs_commit_android14_6_1: ${{ steps.resolve.outputs.susfs_commit_android14_6_1 }}
       susfs_commit_android15_6_6: ${{ steps.resolve.outputs.susfs_commit_android15_6_6 }}
       susfs_commit_android16_6_12: ${{ steps.resolve.outputs.susfs_commit_android16_6_12 }}
+      classic_commit: ${{ steps.resolve.outputs.classic_commit }}
+      resukisu_commit: ${{ steps.resolve.outputs.resukisu_commit }}
       kernel_patches_commit: ${{ steps.resolve.outputs.kernel_patches_commit }}
       anykernel3_commit: ${{ steps.resolve.outputs.anykernel3_commit }}
       droidspaces_commit: ${{ steps.resolve.outputs.droidspaces_commit }}
@@ -228,6 +231,8 @@ jobs:
           # builds at latest.
           CLASSIC_COMMIT="$(pick "$PIN_CLASSIC" https://github.com/tiann/KernelSU.git refs/heads/main)"
           RESUKISU_COMMIT="$(pick "$PIN_RESUKISU" https://github.com/ReSukiSU/ReSukiSU.git refs/heads/main)"
+          emit classic_commit "$CLASSIC_COMMIT"
+          emit resukisu_commit "$RESUKISU_COMMIT"
           if [ "${{ inputs.use_susfs }}" = "true" ]; then
             NEXT_COMMIT="$(resolve_sha https://github.com/pershoot/KernelSU-Next.git refs/heads/dev-susfs)"
           else
@@ -768,6 +773,71 @@ jobs:
       os_patch_level: ${{ inputs.os_patch_level }}
     secrets: inherit
 
+  update-verified-pins:
+    if: ${{ !inputs.test_release_notes && inputs.commit_mode == 'update' }}
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: write
+    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
+    steps:
+    - uses: actions/checkout@v7
+      with:
+        ref: ${{ github.ref }}
+        token: ${{ secrets.GITHUB_TOKEN }}
+
+    - name: Promote verified pins on success
+      id: promote
+      shell: bash
+      run: |
+        set -euo pipefail
+        python3 .github/scripts/update_verified_pins.py
+      env:
+        GH_TOKEN: ${{ github.token }}
+        NOMOUNT_SHA: ${{ needs.resolve-sources.outputs.nomount_commit }}
+        CLASSIC_SHA: ${{ needs.resolve-sources.outputs.classic_commit }}
+        RESUKISU_SHA: ${{ needs.resolve-sources.outputs.resukisu_commit }}
+        SUSFS_ANDROID12_5_10_SHA: ${{ needs.resolve-sources.outputs.susfs_commit_android12_5_10 }}
+        SUSFS_ANDROID13_5_10_SHA: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_10 }}
+        SUSFS_ANDROID13_5_15_SHA: ${{ needs.resolve-sources.outputs.susfs_commit_android13_5_15 }}
+        SUSFS_ANDROID14_5_15_SHA: ${{ needs.resolve-sources.outputs.susfs_commit_android14_5_15 }}
+        SUSFS_ANDROID14_6_1_SHA: ${{ needs.resolve-sources.outputs.susfs_commit_android14_6_1 }}
+        SUSFS_ANDROID15_6_6_SHA: ${{ needs.resolve-sources.outputs.susfs_commit_android15_6_6 }}
+        SUSFS_ANDROID16_6_12_SHA: ${{ needs.resolve-sources.outputs.susfs_commit_android16_6_12 }}
+        APPROVE_NOMOUNT: ${{ inputs.use_nomount }}
+        APPROVE_CLASSIC: ${{ (inputs.root_flavor == 'KernelSU' || inputs.root_flavor == 'All') }}
+        APPROVE_RESUKISU: ${{ (inputs.root_flavor == 'ReSukiSU' || inputs.root_flavor == 'All') }}
+        APPROVE_SUSFS_ANDROID12_5_10: ${{ inputs.use_susfs && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android12-5.10') }}
+        APPROVE_SUSFS_ANDROID13_5_10: ${{ inputs.use_susfs && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android13-5.10') }}
+        APPROVE_SUSFS_ANDROID13_5_15: ${{ inputs.use_susfs && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android13-5.15') }}
+        APPROVE_SUSFS_ANDROID14_5_15: ${{ inputs.use_susfs && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android14-5.15') }}
+        APPROVE_SUSFS_ANDROID14_6_1: ${{ inputs.use_susfs && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android14-6.1') }}
+        APPROVE_SUSFS_ANDROID15_6_6: ${{ inputs.use_susfs && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android15-6.6') }}
+        APPROVE_SUSFS_ANDROID16_6_12: ${{ inputs.use_susfs && (inputs.kernel_build_version == 'All' || inputs.kernel_build_version == 'android16-6.12') }}
+
+    - name: Commit promoted pins
+      if: ${{ always() && steps.promote.conclusion == 'success' }}
+      shell: bash
+      run: |
+        set -euo pipefail
+        git config user.name "github-actions[bot]"
+        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+        STAMP="$(date -u +%Y-%m-%dT%H%M%SZ)"
+        git add .github/workflows/main.yml .github/pins/history
+        if git diff --cached --quiet; then
+          echo "No pin changes to commit."
+          exit 0
+        fi
+        git commit -m "chore(pins): promote verified commits to latest built (${STAMP})"
+        git push
   rej:
     if: ${{ !inputs.test_release_notes && always() }}
     runs-on: ubuntu-latest