From 1e115f78a9b49b7a2e26acde1796662c74c10489 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 23 Jun 2026 19:37:42 +0200 Subject: [PATCH 01/11] Support release-branch targeting in Skia upstream sync Let the Skia sync workflow and update-skia skill target a release/..x line (in both mono/SkiaSharp and mono/skia) instead of only main, derived automatically from the resolved milestone. - auto-skia-sync.md: pre-activation detects the release line from the target milestone, exposes is_release/base_branch/skia_base_branch/ head_branch, fails fast if the mono/skia release branch is missing, and adapts the skip-check; host submodule-align step and prompt body use the resolved base/head branches. - skia-sync-push-prs.sh: consume HEAD_BRANCH/BASE_BRANCH/SKIA_BASE_BRANCH/ IS_RELEASE and target the right base branches with release-aware titles. - update-skia skill: document release-target mode (Phase 1 base detection, parametric Phase 4 + Phase 11, fail-if-missing mono/skia branch). Creation of the matching mono/skia release branch is owned by the release process and will be handled in a separate change to the release-branch skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .agents/skills/update-skia/SKILL.md | 106 +++++--- .github/scripts/skia-sync-push-prs.sh | 20 +- .github/workflows/auto-skia-sync.lock.yml | 284 +++++++++++++++++++--- .github/workflows/auto-skia-sync.md | 184 +++++++++++--- 4 files changed, 484 insertions(+), 110 deletions(-) diff --git a/.agents/skills/update-skia/SKILL.md b/.agents/skills/update-skia/SKILL.md index bdd35ecadda..219326fbba5 100644 --- a/.agents/skills/update-skia/SKILL.md +++ b/.agents/skills/update-skia/SKILL.md @@ -131,7 +131,37 @@ E. Ship (Phase 11) > **Note:** When this phase is pre-computed by the automated workflow, you still need to > add the `upstream` remote and fetch `chrome/m{TARGET}` β€” Phase 5 depends on it. -> πŸ›‘ **GATE**: Confirm current milestone, target milestone, and that upstream branch exists. +5. **Determine the base branch (`main` vs a release line).** Most updates target `main` + (newest, in-development line) with `skiasharp` as the mono/skia base. But when the target + milestone is **older** than `main`'s milestone AND a matching release branch exists, the + update targets that release line instead: + + ```bash + # SkiaSharp uses release/..x (e.g. release/4.148.x for m148) + git ls-remote --heads origin "refs/heads/release/*.{TARGET}.x" + # mono/skia mirrors the SAME branch name + git -C externals/skia ls-remote --heads origin "refs/heads/release/*.{TARGET}.x" + ``` + + | Variable | `main` target | release-line target | + |----------|---------------|---------------------| + | `{BASE_BRANCH}` (SkiaSharp) | `main` | `release/.{TARGET}.x` | + | `{SKIA_BASE_BRANCH}` (mono/skia) | `skiasharp` | `release/.{TARGET}.x` | + | `{HEAD_BRANCH}` (both repos) | `skia-sync/m{TARGET}` | `skia-sync/release-.{TARGET}.x` | + + A release-line update is always a **bug-fix-only sync** (`CURRENT == TARGET`): do NOT bump + the milestone/soname/nuget version in Phase 6 β€” only `cgmanifest.json`'s commit hash changes. + + > πŸ›‘ **The release branch must already exist in BOTH repos.** Branch *creation* is owned by + > the release process (`release-branch` skill), not this skill. If the SkiaSharp + > `release/.{TARGET}.x` branch exists but the mono/skia one does NOT, **STOP and fail** β€” + > do not create it here. Ask a human to cut the mono/skia release branch first. + + Use these `{BASE_BRANCH}` / `{SKIA_BASE_BRANCH}` / `{HEAD_BRANCH}` values everywhere below in + place of the hardcoded `main` / `skiasharp` / `skia-sync/m{TARGET}` defaults. + +> πŸ›‘ **GATE**: Confirm current milestone, target milestone, the base branch (main vs release line, +> with the mono/skia base branch confirmed to exist), and that the upstream branch exists. ### Phase 2: Breaking Change Analysis @@ -197,27 +227,30 @@ milestone numbers and paste your breaking change analysis table. The default exp > ⚠️ **This phase creates BOTH branches before making ANY changes.** You may be on a > workflow branch, a feature branch, or a detached HEAD β€” none of which is the right base. -> You MUST branch from `origin/main` (parent) and `origin/skiasharp` (submodule). +> You MUST branch from `origin/{BASE_BRANCH}` (parent) and `origin/{SKIA_BASE_BRANCH}` +> (submodule). For a `main` update those are `origin/main` and `origin/skiasharp`; for a +> release-line update they are `origin/release/.{TARGET}.x` in both repos +> (see Phase 1 step 5). **Parent repo (SkiaSharp):** -1. **Fetch the latest `main`:** +1. **Fetch the latest base branch:** ```bash - git fetch origin main + git fetch origin {BASE_BRANCH} ``` -2. **Create the feature branch from `origin/main`:** +2. **Create the feature branch from `origin/{BASE_BRANCH}`:** ```bash - git checkout -b skia-sync/m{TARGET} origin/main + git checkout -b {HEAD_BRANCH} origin/{BASE_BRANCH} ``` If the branch already exists on the remote, check it out instead: ```bash - git fetch origin skia-sync/m{TARGET} && git checkout skia-sync/m{TARGET} + git fetch origin {HEAD_BRANCH} && git checkout {HEAD_BRANCH} ``` -3. **Verify you are on the correct branch and it is based on `origin/main`:** +3. **Verify you are on the correct branch and it is based on `origin/{BASE_BRANCH}`:** ```bash - git log --oneline -1 origin/main + git log --oneline -1 origin/{BASE_BRANCH} git log --oneline -1 HEAD ``` These should show the same commit (or HEAD should be ahead by only your own commits). @@ -229,34 +262,35 @@ milestone numbers and paste your breaking change analysis table. The default exp cd externals/skia ``` -5. **Align to the SHA that `origin/main` expects** (the submodule tracks the `skiasharp` - branch in mono/skia, NOT `main`): +5. **Align to the SHA that `origin/{BASE_BRANCH}` expects** (the submodule tracks + `{SKIA_BASE_BRANCH}` in mono/skia β€” `skiasharp` for a `main` update, or the matching + `release/.{TARGET}.x` for a release-line update β€” NOT `main`): ```bash - MAIN_SUB_SHA=$(git -C ../.. ls-tree origin/main -- externals/skia | awk '{print $3}') - git fetch origin skiasharp - git checkout "$MAIN_SUB_SHA" + BASE_SUB_SHA=$(git -C ../.. ls-tree origin/{BASE_BRANCH} -- externals/skia | awk '{print $3}') + git fetch origin {SKIA_BASE_BRANCH} + git checkout "$BASE_SUB_SHA" ``` - Verify this SHA is on `origin/skiasharp`: + Verify this SHA is on `origin/{SKIA_BASE_BRANCH}`: ```bash - git branch -r --contains "$MAIN_SUB_SHA" | grep 'origin/skiasharp' + git branch -r --contains "$BASE_SUB_SHA" | grep 'origin/{SKIA_BASE_BRANCH}' ``` 6. **Create the submodule feature branch:** ```bash - git checkout -b skia-sync/m{TARGET} + git checkout -b {HEAD_BRANCH} ``` > ⚠️ **Do NOT skip the SHA alignment step (step 5).** If the submodule is at a different -> SHA than `origin/main` expects, the merge will produce phantom diffs β€” functions that -> already exist on `main` will appear as new or removed. +> SHA than `origin/{BASE_BRANCH}` expects, the merge will produce phantom diffs β€” functions that +> already exist on the base branch will appear as new or removed. > πŸ›‘ **GATE**: Both branches created. Verify: > ```bash > # In parent repo: -> git rev-parse --abbrev-ref HEAD # β†’ skia-sync/m{TARGET} -> git merge-base HEAD origin/main # β†’ should match origin/main tip +> git rev-parse --abbrev-ref HEAD # β†’ {HEAD_BRANCH} +> git merge-base HEAD origin/{BASE_BRANCH} # β†’ should match origin/{BASE_BRANCH} tip > # In submodule: -> git -C externals/skia rev-parse --abbrev-ref HEAD # β†’ skia-sync/m{TARGET} +> git -C externals/skia rev-parse --abbrev-ref HEAD # β†’ {HEAD_BRANCH} > ``` ### Phase 5: Upstream Merge (mono/skia) @@ -520,22 +554,28 @@ before the update can be considered complete. Do not create PRs with only smoke > **Same-milestone bug-fix syncs:** When `CURRENT == TARGET`, only `cgmanifest.json`'s > `commitHash`/`upstream_merge_commit` change. Use PR titles like > `[skia-sync] Merge upstream chrome/m{TARGET} bug fixes` instead of milestone-bump titles. +> A release-line update is always such a sync. + +> **Release-line targets:** Use the `{BASE_BRANCH}` / `{SKIA_BASE_BRANCH}` / `{HEAD_BRANCH}` +> values resolved in Phase 1 step 5. For a `main` update they are `main` / `skiasharp` / +> `skia-sync/m{TARGET}`; for a release-line update they are `release/.{TARGET}.x` (both +> repos) / `skia-sync/release-.{TARGET}.x`. #### PR 1: mono/skia (submodule) | Field | Value | |-------|-------| -| Branch | `skia-sync/m{TARGET}` | -| Target | `skiasharp` | +| Branch | `{HEAD_BRANCH}` | +| Target | `{SKIA_BASE_BRANCH}` | | Title | `[skia-sync] Merge upstream chrome/m{TARGET}` | #### PR 2: mono/SkiaSharp (parent) | Field | Value | |-------|-------| -| Branch | `skia-sync/m{TARGET}` | -| Target | `main` | -| Title | `[skia-sync] Update skia to milestone {TARGET}` | +| Branch | `{HEAD_BRANCH}` | +| Target | `{BASE_BRANCH}` | +| Title | `[skia-sync] Update skia to milestone {TARGET}` (or `Merge upstream chrome/m{TARGET} bug fixes` when `CURRENT == TARGET`) | **Submodule must point to the mono/skia PR branch.** @@ -548,9 +588,9 @@ Both PRs must reference each other. Before proceeding to merge, verify ALL of these: -- [ ] Branch names follow `skia-sync/m{TARGET}` convention in BOTH repos -- [ ] mono/skia PR targets `skiasharp` branch -- [ ] mono/SkiaSharp PR targets `main` branch +- [ ] Branch names follow `{HEAD_BRANCH}` convention in BOTH repos +- [ ] mono/skia PR targets `{SKIA_BASE_BRANCH}` branch +- [ ] mono/SkiaSharp PR targets `{BASE_BRANCH}` branch - [ ] **SkiaSharp's `externals/skia` submodule points to the mono/skia PR branch** (`git submodule status`) - [ ] `cgmanifest.json` updated with new commit hash, version, and chrome_milestone - [ ] `scripts/VERSIONS.txt` updated (ALL version lines, not just milestone) @@ -562,7 +602,7 @@ Before proceeding to merge, verify ALL of these: #### Merge Sequence (CRITICAL) -1. Merge mono/skia PR first β†’ creates new squashed SHA on `skiasharp` +1. Merge mono/skia PR first β†’ creates new squashed SHA on `{SKIA_BASE_BRANCH}` 2. Fetch new SHA in SkiaSharp's submodule 3. Update submodule pointer, push to SkiaSharp PR branch 4. **Only then** merge SkiaSharp PR @@ -572,12 +612,12 @@ Before proceeding to merge, verify ALL of these: Before proceeding past each step, verify: - [ ] mono/skia PR merged -- [ ] Fetched `skiasharp` branch to get new squashed SHA +- [ ] Fetched `{SKIA_BASE_BRANCH}` branch to get new squashed SHA - [ ] Updated SkiaSharp submodule to new SHA (`cd externals/skia && git fetch origin && git checkout {new-sha}`) - [ ] Pushed submodule update to SkiaSharp PR branch - [ ] CI passes on updated SkiaSharp PR - [ ] SkiaSharp PR merged -- [ ] **Submodule points to a commit on `skiasharp` branch** (not an orphaned branch commit) +- [ ] **Submodule points to a commit on `{SKIA_BASE_BRANCH}` branch** (not an orphaned branch commit) > ❌ **NEVER** merge both PRs without updating the submodule in between. > ❌ **NEVER** assume the submodule reference is correct after squash-merging mono/skia. diff --git a/.github/scripts/skia-sync-push-prs.sh b/.github/scripts/skia-sync-push-prs.sh index 7bc2a8d6bbb..d004554650a 100755 --- a/.github/scripts/skia-sync-push-prs.sh +++ b/.github/scripts/skia-sync-push-prs.sh @@ -6,7 +6,8 @@ # GH_TOKEN β€” PAT with write access to mono/skia and mono/SkiaSharp # # Expected files (written by the agent): -# /tmp/gh-aw/agent/skia-sync-env.sh β€” TARGET and CURRENT vars +# /tmp/gh-aw/agent/skia-sync-env.sh β€” TARGET, CURRENT, and (optionally) IS_RELEASE, +# BASE_BRANCH, SKIA_BASE_BRANCH, HEAD_BRANCH vars # /tmp/gh-aw/agent/skia-sync-skia-summary.md β€” mono/skia PR body # /tmp/gh-aw/agent/skia-sync-skiasharp-summary.md β€” mono/SkiaSharp PR body # @@ -24,7 +25,15 @@ if [ -z "${TARGET:-}" ]; then exit 0 fi -BRANCH="skia-sync/m${TARGET}" +# Branch targets. For a main sync these default to the historical values; for a +# release-line sync the agent provides explicit HEAD_BRANCH / BASE_BRANCH / +# SKIA_BASE_BRANCH that point at the matching release/..x line. +BRANCH="${HEAD_BRANCH:-skia-sync/m${TARGET}}" +SS_BASE="${BASE_BRANCH:-main}" +SKIA_BASE="${SKIA_BASE_BRANCH:-skiasharp}" +IS_RELEASE="${IS_RELEASE:-false}" +echo "Sync branch: $BRANCH | SkiaSharp base: $SS_BASE | mono/skia base: $SKIA_BASE | release: $IS_RELEASE" + SKIA_SUMMARY="" SS_SUMMARY="" [ -f /tmp/gh-aw/agent/skia-sync-skia-summary.md ] && SKIA_SUMMARY=$(cat /tmp/gh-aw/agent/skia-sync-skia-summary.md) @@ -38,6 +47,9 @@ else SS_TITLE="[skia-sync] Update skia to milestone ${TARGET}" SS_BODY_INTRO="Automated Skia milestone bump from m${CURRENT} to m${TARGET}." fi +if [ "$IS_RELEASE" = "true" ]; then + SS_BODY_INTRO="${SS_BODY_INTRO} Targeting release branch \`${SS_BASE}\` (mono/skia \`${SKIA_BASE}\`)." +fi WORKFLOW_LINK="[skia-upstream-sync](https://github.com/${GITHUB_REPOSITORY:-mono/SkiaSharp}/actions/workflows/auto-skia-sync.lock.yml)" @@ -58,7 +70,7 @@ push_skia() { if [ -z "$pr" ]; then echo "Creating mono/skia PR..." gh pr create --repo mono/skia \ - --head "$BRANCH" --base skiasharp \ + --head "$BRANCH" --base "$SKIA_BASE" \ --title "[skia-sync] Merge upstream chrome/m${TARGET}" \ --draft \ --body "Automated upstream merge of \`chrome/m${TARGET}\`. @@ -98,7 +110,7 @@ push_skiasharp() { if [ -z "$ss_pr" ]; then echo "Creating mono/SkiaSharp PR..." gh pr create --repo mono/SkiaSharp \ - --head "$BRANCH" --base main \ + --head "$BRANCH" --base "$SS_BASE" \ --title "$SS_TITLE" \ --draft \ --body "${SS_BODY_INTRO} diff --git a/.github/workflows/auto-skia-sync.lock.yml b/.github/workflows/auto-skia-sync.lock.yml index f154c8d36fc..595e0b9afe6 100644 --- a/.github/workflows/auto-skia-sync.lock.yml +++ b/.github/workflows/auto-skia-sync.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6c9eafdff2e1546bd8645fa801fb42c9301e9f2305a09e3d7e27ab9a405be143","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c6bbf9789260e93f5f0c4327813b2def5a3da617e56dff527364a7e3f268a448","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -77,11 +77,11 @@ name: "Skia Upstream Sync" # fi # echo "mode=$MODE" >> "$GITHUB_OUTPUT" # - # CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ + # # main's milestone β€” the basis for `current`/`next` mode math. + # MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ # --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - # echo "current=$CURRENT" >> "$GITHUB_OUTPUT" # - # NEXT=$((CURRENT + 1)) + # NEXT=$((MAIN_MS + 1)) # echo "next=$NEXT" >> "$GITHUB_OUTPUT" # # LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ @@ -96,12 +96,56 @@ name: "Skia Upstream Sync" # elif [ "$MODE" = "latest" ]; then # TARGET="$LATEST" # elif [ "$MODE" = "current" ]; then - # TARGET="$CURRENT" + # TARGET="$MAIN_MS" # else # TARGET="$NEXT" # fi # echo "target=$TARGET" >> "$GITHUB_OUTPUT" # + # # -- Resolve the target line: `main` (newest, in-development) or a release line. + # # A maintenance line lives as `release/..x` in BOTH mono/SkiaSharp + # # and mono/skia. We only look for one when TARGET is older than main's milestone β€” + # # the newest line is always served by `main`. The release branch itself is created + # # by the release process (release-branch skill), NOT this sync; if the mono/skia + # # side is missing we fail so branch ownership stays with the release process. + # IS_RELEASE=false + # BASE_BRANCH=main + # SKIA_BASE_BRANCH=skiasharp + # HEAD_BRANCH="skia-sync/m${TARGET}" + # RELEASE_BRANCH="" + # if [ "$TARGET" != "$MAIN_MS" ]; then + # RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ + # | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) + # fi + # if [ -n "$RELEASE_BRANCH" ]; then + # IS_RELEASE=true + # BASE_BRANCH="$RELEASE_BRANCH" + # SKIA_BASE_BRANCH="$RELEASE_BRANCH" + # HEAD_BRANCH="skia-sync/${RELEASE_BRANCH//\//-}" + # + # # The matching mono/skia release branch MUST already exist. + # SKIA_BASE_SHA=$(git ls-remote --heads https://github.com/mono/skia.git "refs/heads/${SKIA_BASE_BRANCH}" | awk '{print $1}') + # if [ -z "$SKIA_BASE_SHA" ]; then + # echo "::error::mono/skia branch '${SKIA_BASE_BRANCH}' does not exist. Release branches are owned by the release process (release-branch skill) β€” create it before running a release sync for m${TARGET}." + # exit 1 + # fi + # fi + # echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT" + # echo "base_branch=$BASE_BRANCH" >> "$GITHUB_OUTPUT" + # echo "skia_base_branch=$SKIA_BASE_BRANCH" >> "$GITHUB_OUTPUT" + # echo "head_branch=$HEAD_BRANCH" >> "$GITHUB_OUTPUT" + # + # # `current` is the milestone of the BASE branch we're syncing INTO: + # # - main line β†’ main's milestone + # # - release β†’ that line's milestone (== TARGET β‡’ a bug-fix-only sync) + # if [ "$IS_RELEASE" = true ]; then + # CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=${BASE_BRANCH}" \ + # --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') + # else + # CURRENT="$MAIN_MS" + # fi + # echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + # # UPSTREAM_SHA=$(git ls-remote https://github.com/google/skia.git "refs/heads/chrome/m${TARGET}" | awk '{print $1}') # if [ -z "$UPSTREAM_SHA" ]; then # echo "::notice::upstream/chrome/m${TARGET} does not exist yet" @@ -112,21 +156,30 @@ name: "Skia Upstream Sync" # exit 0 # fi # - # # Check if the sync branch already contains all upstream commits. - # # This avoids spinning up the expensive agent job when there's nothing new. - # SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/skia-sync/m${TARGET}" | awk '{print $1}') + # # Check whether there is anything new to merge from upstream. Compare upstream + # # HEAD against the existing sync branch if present; otherwise, for release lines, + # # against the release base branch. This avoids spinning up the expensive agent + # # job when there's nothing new. (For a main milestone bump the sync branch won't + # # exist yet and there is always new work, so we proceed.) + # SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/${HEAD_BRANCH}" | awk '{print $1}') + # COMPARE_REF="" # if [ -n "$SYNC_SHA" ]; then - # BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...skia-sync/m${TARGET}" \ + # COMPARE_REF="$HEAD_BRANCH" + # elif [ "$IS_RELEASE" = true ]; then + # COMPARE_REF="$SKIA_BASE_BRANCH" + # fi + # if [ -n "$COMPARE_REF" ]; then + # BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...${COMPARE_REF}" \ # --jq '.behind_by' 2>/dev/null || echo "unknown") # if [ "$BEHIND" = "0" ]; then - # echo "::notice::chrome/m${TARGET} already fully merged into skia-sync/m${TARGET} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" + # echo "::notice::chrome/m${TARGET} already fully merged into ${COMPARE_REF} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" # echo "skip=true" >> "$GITHUB_OUTPUT" # exit 0 # fi - # echo "Sync branch exists but is ${BEHIND} commits behind upstream" + # echo "${COMPARE_REF} exists but is ${BEHIND} commits behind upstream" # fi # - # echo "Will process: m${TARGET} (mode=${MODE}, current=m${CURRENT}, latest=m${LATEST})" + # echo "Will process: m${TARGET} β†’ ${BASE_BRANCH} (mode=${MODE}, base milestone=m${CURRENT}, latest=m${LATEST}, head=${HEAD_BRANCH})" workflow_dispatch: inputs: aw_context: @@ -277,29 +330,33 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: ${{ needs.pre_activation.outputs.base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_CURRENT: ${{ needs.pre_activation.outputs.current }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_HEAD_BRANCH: ${{ needs.pre_activation.outputs.head_branch }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_IS_RELEASE: ${{ needs.pre_activation.outputs.is_release }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_SKIA_BASE_BRANCH: ${{ needs.pre_activation.outputs.skia_base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_TARGET: ${{ needs.pre_activation.outputs.target }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9a538b67d4f587c5_EOF' + cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' - GH_AW_PROMPT_9a538b67d4f587c5_EOF + GH_AW_PROMPT_a175c22ffc6eacbc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_9a538b67d4f587c5_EOF' + cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_9a538b67d4f587c5_EOF + GH_AW_PROMPT_a175c22ffc6eacbc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_9a538b67d4f587c5_EOF' + cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' - GH_AW_PROMPT_9a538b67d4f587c5_EOF + GH_AW_PROMPT_a175c22ffc6eacbc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9a538b67d4f587c5_EOF' + cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -331,19 +388,23 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_9a538b67d4f587c5_EOF + GH_AW_PROMPT_a175c22ffc6eacbc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_9a538b67d4f587c5_EOF' + cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' {{#runtime-import .github/workflows/auto-skia-sync.md}} - GH_AW_PROMPT_9a538b67d4f587c5_EOF + GH_AW_PROMPT_a175c22ffc6eacbc_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: ${{ needs.pre_activation.outputs.base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_CURRENT: ${{ needs.pre_activation.outputs.current }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_HEAD_BRANCH: ${{ needs.pre_activation.outputs.head_branch }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_IS_RELEASE: ${{ needs.pre_activation.outputs.is_release }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_SKIA_BASE_BRANCH: ${{ needs.pre_activation.outputs.skia_base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_TARGET: ${{ needs.pre_activation.outputs.target }} with: script: | @@ -365,7 +426,11 @@ jobs: GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` β€” run `safeoutputs --help` to see available tools' GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: ${{ needs.pre_activation.outputs.base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_CURRENT: ${{ needs.pre_activation.outputs.current }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_HEAD_BRANCH: ${{ needs.pre_activation.outputs.head_branch }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_IS_RELEASE: ${{ needs.pre_activation.outputs.is_release }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_SKIA_BASE_BRANCH: ${{ needs.pre_activation.outputs.skia_base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_TARGET: ${{ needs.pre_activation.outputs.target }} with: script: | @@ -388,7 +453,11 @@ jobs: GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_CURRENT: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_CURRENT, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_HEAD_BRANCH: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_HEAD_BRANCH, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_IS_RELEASE: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_IS_RELEASE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_SKIA_BASE_BRANCH: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_SKIA_BASE_BRANCH, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_TARGET: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_TARGET } }); @@ -475,14 +544,88 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} + - name: Start DIFC Proxy + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + DIFC_PROXY_POLICY: '{"allow-only":{"min-integrity":"none","repos":["mono/skia","mono/skiasharp"]}}' + DIFC_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.3.6' + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh" - name: Set up agent output directory run: | mkdir -p /tmp/gh-aw/agent - - name: Align submodule to origin/main - run: "# The checkout uses the workflow branch, so the submodule may be at a\n# different SHA than what origin/main expects. Fix it before the agent runs.\n# Note: the submodule tracks the `skiasharp` branch in mono/skia, so this\n# SHA should be a commit on origin/skiasharp.\nMAIN_SUB_SHA=$(git ls-tree origin/main -- externals/skia | awk '{print $3}')\necho \"origin/main submodule SHA: $MAIN_SUB_SHA\"\ngit -C externals/skia fetch origin skiasharp 2>&1\ngit -C externals/skia checkout \"$MAIN_SUB_SHA\" 2>&1\necho \"Verifying SHA is on skiasharp branch:\"\ngit -C externals/skia branch -r --contains \"$MAIN_SUB_SHA\" | grep -q 'origin/skiasharp' \\\n && echo \" βœ… SHA is on origin/skiasharp\" \\\n || echo \" ⚠️ SHA is NOT on origin/skiasharp β€” submodule pointer may be stale\"\n" + env: + GH_HOST: localhost:18443 + GH_REPO: ${{ github.repository }} + GITHUB_API_URL: https://localhost:18443/api/v3 + GITHUB_GRAPHQL_URL: https://localhost:18443/api/graphql + NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt + - name: Align submodule to the base branch + run: | + # Re-derive the sync base branch here (same rules as the pre-activation + # `Detect milestone` step, which stays the source of truth). We can't read + # its outputs: this step runs in the agent job, which only `needs:` + # activation, and the cheap pre-activation job has no checkout to share a + # script from. github.event inputs ARE available in any job, so we redo the + # small base-branch resolution from them. + if [ -n "$INPUT_MODE" ]; then MODE="$INPUT_MODE"; + elif [ "$SCHEDULE" = "0 7 * * *" ]; then MODE=current; + elif [ "$SCHEDULE" = "0 17 * * *" ]; then MODE=latest; + else MODE=next; fi + MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ + --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') + if [ -n "$INPUT_MILESTONE" ]; then TARGET="$INPUT_MILESTONE"; + elif [ "$MODE" = "latest" ]; then + TARGET=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ + | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' | sort -n | tail -1); + elif [ "$MODE" = "current" ]; then TARGET="$MAIN_MS"; + else TARGET="$((MAIN_MS + 1))"; fi + + BASE_BRANCH=main + SKIA_BASE_BRANCH=skiasharp + if [ "$TARGET" != "$MAIN_MS" ]; then + RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ + | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) + if [ -n "$RELEASE_BRANCH" ]; then + BASE_BRANCH="$RELEASE_BRANCH" + SKIA_BASE_BRANCH="$RELEASE_BRANCH" + fi + fi + + # The checkout uses the workflow branch (main), so the submodule may be at a + # different SHA than the base branch expects. Fix it before the agent runs. + # The submodule tracks `${SKIA_BASE_BRANCH}` in mono/skia (skiasharp for a + # main sync, release/..x for a release sync), so the + # base-branch submodule SHA should be a commit on that branch. + echo "Aligning submodule to origin/${BASE_BRANCH} (mono/skia ${SKIA_BASE_BRANCH})" + git fetch origin "$BASE_BRANCH" 2>&1 || true + BASE_SUB_SHA=$(git ls-tree "origin/${BASE_BRANCH}" -- externals/skia | awk '{print $3}') + echo "origin/${BASE_BRANCH} submodule SHA: $BASE_SUB_SHA" + git -C externals/skia fetch origin "$SKIA_BASE_BRANCH" 2>&1 + git -C externals/skia checkout "$BASE_SUB_SHA" 2>&1 + echo "Verifying SHA is on ${SKIA_BASE_BRANCH} branch:" + git -C externals/skia branch -r --contains "$BASE_SUB_SHA" | grep -q "origin/${SKIA_BASE_BRANCH}" \ + && echo " βœ… SHA is on origin/${SKIA_BASE_BRANCH}" \ + || echo " ⚠️ SHA is NOT on origin/${SKIA_BASE_BRANCH} β€” submodule pointer may be stale" + env: + GH_HOST: localhost:18443 + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + GITHUB_API_URL: https://localhost:18443/api/v3 + GITHUB_GRAPHQL_URL: https://localhost:18443/api/graphql + INPUT_MILESTONE: ${{ github.event.inputs.milestone }} + INPUT_MODE: ${{ github.event.inputs.mode }} + NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt + SCHEDULE: ${{ github.event.schedule }} - name: Copy push script for post-step run: cp .github/scripts/skia-sync-push-prs.sh /tmp/gh-aw/skia-sync-push-prs.sh - + env: + GH_HOST: localhost:18443 + GH_REPO: ${{ github.repository }} + GITHUB_API_URL: https://localhost:18443/api/v3 + GITHUB_GRAPHQL_URL: https://localhost:18443/api/graphql + NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} @@ -523,6 +666,10 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Stop DIFC Proxy + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/stop_difc_proxy.sh" - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -550,9 +697,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_270162c4ead1b8eb_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_9b1865f00eeb8608_EOF' {"create_issue":{"labels":["auto-skia-sync"],"max":1,"title_prefix":"[auto-skia-sync]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_270162c4ead1b8eb_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_9b1865f00eeb8608_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -750,7 +897,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_43a43904c0849692_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_22666b6fb299eac2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -798,7 +945,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_43a43904c0849692_EOF + GH_AW_MCP_CONFIG_22666b6fb299eac2_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1358,13 +1505,17 @@ jobs: runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + base_branch: ${{ steps.detect.outputs.base_branch }} current: ${{ steps.detect.outputs.current }} detect_result: ${{ steps.detect.outcome }} + head_branch: ${{ steps.detect.outputs.head_branch }} + is_release: ${{ steps.detect.outputs.is_release }} latest: ${{ steps.detect.outputs.latest }} matched_command: '' mode: ${{ steps.detect.outputs.mode }} next: ${{ steps.detect.outputs.next }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + skia_base_branch: ${{ steps.detect.outputs.skia_base_branch }} skip: ${{ steps.detect.outputs.skip }} target: ${{ steps.detect.outputs.target }} steps: @@ -1404,11 +1555,11 @@ jobs: fi echo "mode=$MODE" >> "$GITHUB_OUTPUT" - CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ + # main's milestone β€” the basis for `current`/`next` mode math. + MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - echo "current=$CURRENT" >> "$GITHUB_OUTPUT" - NEXT=$((CURRENT + 1)) + NEXT=$((MAIN_MS + 1)) echo "next=$NEXT" >> "$GITHUB_OUTPUT" LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ @@ -1423,12 +1574,56 @@ jobs: elif [ "$MODE" = "latest" ]; then TARGET="$LATEST" elif [ "$MODE" = "current" ]; then - TARGET="$CURRENT" + TARGET="$MAIN_MS" else TARGET="$NEXT" fi echo "target=$TARGET" >> "$GITHUB_OUTPUT" + # -- Resolve the target line: `main` (newest, in-development) or a release line. + # A maintenance line lives as `release/..x` in BOTH mono/SkiaSharp + # and mono/skia. We only look for one when TARGET is older than main's milestone β€” + # the newest line is always served by `main`. The release branch itself is created + # by the release process (release-branch skill), NOT this sync; if the mono/skia + # side is missing we fail so branch ownership stays with the release process. + IS_RELEASE=false + BASE_BRANCH=main + SKIA_BASE_BRANCH=skiasharp + HEAD_BRANCH="skia-sync/m${TARGET}" + RELEASE_BRANCH="" + if [ "$TARGET" != "$MAIN_MS" ]; then + RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ + | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) + fi + if [ -n "$RELEASE_BRANCH" ]; then + IS_RELEASE=true + BASE_BRANCH="$RELEASE_BRANCH" + SKIA_BASE_BRANCH="$RELEASE_BRANCH" + HEAD_BRANCH="skia-sync/${RELEASE_BRANCH//\//-}" + + # The matching mono/skia release branch MUST already exist. + SKIA_BASE_SHA=$(git ls-remote --heads https://github.com/mono/skia.git "refs/heads/${SKIA_BASE_BRANCH}" | awk '{print $1}') + if [ -z "$SKIA_BASE_SHA" ]; then + echo "::error::mono/skia branch '${SKIA_BASE_BRANCH}' does not exist. Release branches are owned by the release process (release-branch skill) β€” create it before running a release sync for m${TARGET}." + exit 1 + fi + fi + echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT" + echo "base_branch=$BASE_BRANCH" >> "$GITHUB_OUTPUT" + echo "skia_base_branch=$SKIA_BASE_BRANCH" >> "$GITHUB_OUTPUT" + echo "head_branch=$HEAD_BRANCH" >> "$GITHUB_OUTPUT" + + # `current` is the milestone of the BASE branch we're syncing INTO: + # - main line β†’ main's milestone + # - release β†’ that line's milestone (== TARGET β‡’ a bug-fix-only sync) + if [ "$IS_RELEASE" = true ]; then + CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=${BASE_BRANCH}" \ + --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') + else + CURRENT="$MAIN_MS" + fi + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + UPSTREAM_SHA=$(git ls-remote https://github.com/google/skia.git "refs/heads/chrome/m${TARGET}" | awk '{print $1}') if [ -z "$UPSTREAM_SHA" ]; then echo "::notice::upstream/chrome/m${TARGET} does not exist yet" @@ -1439,21 +1634,30 @@ jobs: exit 0 fi - # Check if the sync branch already contains all upstream commits. - # This avoids spinning up the expensive agent job when there's nothing new. - SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/skia-sync/m${TARGET}" | awk '{print $1}') + # Check whether there is anything new to merge from upstream. Compare upstream + # HEAD against the existing sync branch if present; otherwise, for release lines, + # against the release base branch. This avoids spinning up the expensive agent + # job when there's nothing new. (For a main milestone bump the sync branch won't + # exist yet and there is always new work, so we proceed.) + SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/${HEAD_BRANCH}" | awk '{print $1}') + COMPARE_REF="" if [ -n "$SYNC_SHA" ]; then - BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...skia-sync/m${TARGET}" \ + COMPARE_REF="$HEAD_BRANCH" + elif [ "$IS_RELEASE" = true ]; then + COMPARE_REF="$SKIA_BASE_BRANCH" + fi + if [ -n "$COMPARE_REF" ]; then + BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...${COMPARE_REF}" \ --jq '.behind_by' 2>/dev/null || echo "unknown") if [ "$BEHIND" = "0" ]; then - echo "::notice::chrome/m${TARGET} already fully merged into skia-sync/m${TARGET} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" + echo "::notice::chrome/m${TARGET} already fully merged into ${COMPARE_REF} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi - echo "Sync branch exists but is ${BEHIND} commits behind upstream" + echo "${COMPARE_REF} exists but is ${BEHIND} commits behind upstream" fi - echo "Will process: m${TARGET} (mode=${MODE}, current=m${CURRENT}, latest=m${LATEST})" + echo "Will process: m${TARGET} β†’ ${BASE_BRANCH} (mode=${MODE}, base milestone=m${CURRENT}, latest=m${LATEST}, head=${HEAD_BRANCH})" env: GH_TOKEN: ${{ github.token }} INPUT_MILESTONE: ${{ github.event.inputs.milestone }} diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 6da17ce9842..54b967aaa05 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -47,11 +47,11 @@ on: fi echo "mode=$MODE" >> "$GITHUB_OUTPUT" - CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ + # main's milestone β€” the basis for `current`/`next` mode math. + MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - echo "current=$CURRENT" >> "$GITHUB_OUTPUT" - NEXT=$((CURRENT + 1)) + NEXT=$((MAIN_MS + 1)) echo "next=$NEXT" >> "$GITHUB_OUTPUT" LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ @@ -66,12 +66,56 @@ on: elif [ "$MODE" = "latest" ]; then TARGET="$LATEST" elif [ "$MODE" = "current" ]; then - TARGET="$CURRENT" + TARGET="$MAIN_MS" else TARGET="$NEXT" fi echo "target=$TARGET" >> "$GITHUB_OUTPUT" + # -- Resolve the target line: `main` (newest, in-development) or a release line. + # A maintenance line lives as `release/..x` in BOTH mono/SkiaSharp + # and mono/skia. We only look for one when TARGET is older than main's milestone β€” + # the newest line is always served by `main`. The release branch itself is created + # by the release process (release-branch skill), NOT this sync; if the mono/skia + # side is missing we fail so branch ownership stays with the release process. + IS_RELEASE=false + BASE_BRANCH=main + SKIA_BASE_BRANCH=skiasharp + HEAD_BRANCH="skia-sync/m${TARGET}" + RELEASE_BRANCH="" + if [ "$TARGET" != "$MAIN_MS" ]; then + RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ + | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) + fi + if [ -n "$RELEASE_BRANCH" ]; then + IS_RELEASE=true + BASE_BRANCH="$RELEASE_BRANCH" + SKIA_BASE_BRANCH="$RELEASE_BRANCH" + HEAD_BRANCH="skia-sync/${RELEASE_BRANCH//\//-}" + + # The matching mono/skia release branch MUST already exist. + SKIA_BASE_SHA=$(git ls-remote --heads https://github.com/mono/skia.git "refs/heads/${SKIA_BASE_BRANCH}" | awk '{print $1}') + if [ -z "$SKIA_BASE_SHA" ]; then + echo "::error::mono/skia branch '${SKIA_BASE_BRANCH}' does not exist. Release branches are owned by the release process (release-branch skill) β€” create it before running a release sync for m${TARGET}." + exit 1 + fi + fi + echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT" + echo "base_branch=$BASE_BRANCH" >> "$GITHUB_OUTPUT" + echo "skia_base_branch=$SKIA_BASE_BRANCH" >> "$GITHUB_OUTPUT" + echo "head_branch=$HEAD_BRANCH" >> "$GITHUB_OUTPUT" + + # `current` is the milestone of the BASE branch we're syncing INTO: + # - main line β†’ main's milestone + # - release β†’ that line's milestone (== TARGET β‡’ a bug-fix-only sync) + if [ "$IS_RELEASE" = true ]; then + CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=${BASE_BRANCH}" \ + --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') + else + CURRENT="$MAIN_MS" + fi + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + UPSTREAM_SHA=$(git ls-remote https://github.com/google/skia.git "refs/heads/chrome/m${TARGET}" | awk '{print $1}') if [ -z "$UPSTREAM_SHA" ]; then echo "::notice::upstream/chrome/m${TARGET} does not exist yet" @@ -82,21 +126,30 @@ on: exit 0 fi - # Check if the sync branch already contains all upstream commits. - # This avoids spinning up the expensive agent job when there's nothing new. - SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/skia-sync/m${TARGET}" | awk '{print $1}') + # Check whether there is anything new to merge from upstream. Compare upstream + # HEAD against the existing sync branch if present; otherwise, for release lines, + # against the release base branch. This avoids spinning up the expensive agent + # job when there's nothing new. (For a main milestone bump the sync branch won't + # exist yet and there is always new work, so we proceed.) + SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/${HEAD_BRANCH}" | awk '{print $1}') + COMPARE_REF="" if [ -n "$SYNC_SHA" ]; then - BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...skia-sync/m${TARGET}" \ + COMPARE_REF="$HEAD_BRANCH" + elif [ "$IS_RELEASE" = true ]; then + COMPARE_REF="$SKIA_BASE_BRANCH" + fi + if [ -n "$COMPARE_REF" ]; then + BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...${COMPARE_REF}" \ --jq '.behind_by' 2>/dev/null || echo "unknown") if [ "$BEHIND" = "0" ]; then - echo "::notice::chrome/m${TARGET} already fully merged into skia-sync/m${TARGET} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" + echo "::notice::chrome/m${TARGET} already fully merged into ${COMPARE_REF} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi - echo "Sync branch exists but is ${BEHIND} commits behind upstream" + echo "${COMPARE_REF} exists but is ${BEHIND} commits behind upstream" fi - echo "Will process: m${TARGET} (mode=${MODE}, current=m${CURRENT}, latest=m${LATEST})" + echo "Will process: m${TARGET} β†’ ${BASE_BRANCH} (mode=${MODE}, base milestone=m${CURRENT}, latest=m${LATEST}, head=${HEAD_BRANCH})" # -- Pre-activation outputs ------------------------------------------ # Expose detect step outputs for use in the prompt and other jobs. @@ -110,6 +163,10 @@ jobs: target: ${{ steps.detect.outputs.target }} mode: ${{ steps.detect.outputs.mode }} skip: ${{ steps.detect.outputs.skip }} + is_release: ${{ steps.detect.outputs.is_release }} + base_branch: ${{ steps.detect.outputs.base_branch }} + skia_base_branch: ${{ steps.detect.outputs.skia_base_branch }} + head_branch: ${{ steps.detect.outputs.head_branch }} # -- Agent job gate -------------------------------------------------- # Only run the agent if pre-activation succeeded and there's work to do. @@ -181,20 +238,58 @@ steps: - name: Set up agent output directory run: | mkdir -p /tmp/gh-aw/agent - - name: Align submodule to origin/main + - name: Align submodule to the base branch + env: + INPUT_MODE: ${{ github.event.inputs.mode }} + INPUT_MILESTONE: ${{ github.event.inputs.milestone }} + SCHEDULE: ${{ github.event.schedule }} + GH_TOKEN: ${{ github.token }} run: | - # The checkout uses the workflow branch, so the submodule may be at a - # different SHA than what origin/main expects. Fix it before the agent runs. - # Note: the submodule tracks the `skiasharp` branch in mono/skia, so this - # SHA should be a commit on origin/skiasharp. - MAIN_SUB_SHA=$(git ls-tree origin/main -- externals/skia | awk '{print $3}') - echo "origin/main submodule SHA: $MAIN_SUB_SHA" - git -C externals/skia fetch origin skiasharp 2>&1 - git -C externals/skia checkout "$MAIN_SUB_SHA" 2>&1 - echo "Verifying SHA is on skiasharp branch:" - git -C externals/skia branch -r --contains "$MAIN_SUB_SHA" | grep -q 'origin/skiasharp' \ - && echo " βœ… SHA is on origin/skiasharp" \ - || echo " ⚠️ SHA is NOT on origin/skiasharp β€” submodule pointer may be stale" + # Re-derive the sync base branch here (same rules as the pre-activation + # `Detect milestone` step, which stays the source of truth). We can't read + # its outputs: this step runs in the agent job, which only `needs:` + # activation, and the cheap pre-activation job has no checkout to share a + # script from. github.event inputs ARE available in any job, so we redo the + # small base-branch resolution from them. + if [ -n "$INPUT_MODE" ]; then MODE="$INPUT_MODE"; + elif [ "$SCHEDULE" = "0 7 * * *" ]; then MODE=current; + elif [ "$SCHEDULE" = "0 17 * * *" ]; then MODE=latest; + else MODE=next; fi + MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ + --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') + if [ -n "$INPUT_MILESTONE" ]; then TARGET="$INPUT_MILESTONE"; + elif [ "$MODE" = "latest" ]; then + TARGET=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ + | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' | sort -n | tail -1); + elif [ "$MODE" = "current" ]; then TARGET="$MAIN_MS"; + else TARGET="$((MAIN_MS + 1))"; fi + + BASE_BRANCH=main + SKIA_BASE_BRANCH=skiasharp + if [ "$TARGET" != "$MAIN_MS" ]; then + RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ + | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) + if [ -n "$RELEASE_BRANCH" ]; then + BASE_BRANCH="$RELEASE_BRANCH" + SKIA_BASE_BRANCH="$RELEASE_BRANCH" + fi + fi + + # The checkout uses the workflow branch (main), so the submodule may be at a + # different SHA than the base branch expects. Fix it before the agent runs. + # The submodule tracks `${SKIA_BASE_BRANCH}` in mono/skia (skiasharp for a + # main sync, release/..x for a release sync), so the + # base-branch submodule SHA should be a commit on that branch. + echo "Aligning submodule to origin/${BASE_BRANCH} (mono/skia ${SKIA_BASE_BRANCH})" + git fetch origin "$BASE_BRANCH" 2>&1 || true + BASE_SUB_SHA=$(git ls-tree "origin/${BASE_BRANCH}" -- externals/skia | awk '{print $3}') + echo "origin/${BASE_BRANCH} submodule SHA: $BASE_SUB_SHA" + git -C externals/skia fetch origin "$SKIA_BASE_BRANCH" 2>&1 + git -C externals/skia checkout "$BASE_SUB_SHA" 2>&1 + echo "Verifying SHA is on ${SKIA_BASE_BRANCH} branch:" + git -C externals/skia branch -r --contains "$BASE_SUB_SHA" | grep -q "origin/${SKIA_BASE_BRANCH}" \ + && echo " βœ… SHA is on origin/${SKIA_BASE_BRANCH}" \ + || echo " ⚠️ SHA is NOT on origin/${SKIA_BASE_BRANCH} β€” submodule pointer may be stale" - name: Copy push script for post-step run: | cp .github/scripts/skia-sync-push-prs.sh /tmp/gh-aw/skia-sync-push-prs.sh @@ -225,20 +320,39 @@ post-steps: # Skia Upstream Sync -Current: `m${{ needs.pre_activation.outputs.current }}`. -Target: `m${{ needs.pre_activation.outputs.target }}`. -Branch: `skia-sync/m${{ needs.pre_activation.outputs.target }}`. +Base milestone (current): `m${{ needs.pre_activation.outputs.current }}`. +Target milestone: `m${{ needs.pre_activation.outputs.target }}`. +Base branch (SkiaSharp): `${{ needs.pre_activation.outputs.base_branch }}` β€” mono/skia base: `${{ needs.pre_activation.outputs.skia_base_branch }}`. +Sync (head) branch: `${{ needs.pre_activation.outputs.head_branch }}` (same name in both repos). +Release-line sync: `${{ needs.pre_activation.outputs.is_release }}`. + +> **About the base branch.** Most syncs target `main` (the newest, in-development line) with +> `skiasharp` as the mono/skia base. When an older milestone is requested AND a matching +> `release/..x` branch exists, this is a **release-line sync**: both PRs target +> that release branch instead (mono/skia base = the same `release/..x` branch, +> which the release process guarantees exists). In that case `current == target`, so it is always a +> **bug-fix-only sync** β€” do NOT bump the milestone, soname, or nuget versions; only the upstream +> merge + `cgmanifest.json` commit hash change. Use the base/head branch values above everywhere +> instead of hardcoding `main`/`skiasharp`/`skia-sync/m{target}`. **Read `.agents/skills/update-skia/SKILL.md` and follow Phases 2-10.** Notes specific to this automated workflow: - **Phase 1 is pre-computed** (above). Skip it β€” but you still need to add the `upstream` remote and fetch `chrome/m${{ needs.pre_activation.outputs.target }}` (Phase 1 step 4) since Phase 5 depends on it. - **First thing**: run `dotnet tool restore` (pre-agent-steps can't do this for the chroot). -- **Phase 4**: Before creating a fresh branch, check if `origin/skia-sync/m${{ needs.pre_activation.outputs.target }}` already exists. - If so, check it out β€” the pre-activation step already verified new upstream commits exist. - Even when current == target, there may be new upstream bug-fix commits β€” a matching milestone does NOT mean no work. - **Skip Phase 4 step 5** (submodule SHA alignment) β€” the pre-agent step already aligned it. +- **Phase 4**: The parent base branch is `origin/${{ needs.pre_activation.outputs.base_branch }}` and the + submodule base branch is mono/skia `${{ needs.pre_activation.outputs.skia_base_branch }}` β€” use these in + place of `origin/main` / `skiasharp` for every step. The sync branch in BOTH repos is + `${{ needs.pre_activation.outputs.head_branch }}`. Before creating a fresh branch, check if + `origin/${{ needs.pre_activation.outputs.head_branch }}` already exists; if so, check it out β€” the + pre-activation step already verified new upstream commits exist. Even when current == target there may + be new upstream bug-fix commits β€” a matching milestone does NOT mean no work. + **Skip Phase 4 step 5** (submodule SHA alignment) β€” the pre-agent step already aligned the submodule to + the base branch's pointer (on mono/skia `${{ needs.pre_activation.outputs.skia_base_branch }}`). Branch from the current HEAD when creating the submodule feature branch in step 6. +- **Phase 6 (version files)**: For a release-line sync (`is_release == true`, so `current == target`) this is a + bug-fix-only sync β€” keep the release line's milestone/soname/nuget versions unchanged; the only expected + parent-repo change is `cgmanifest.json`'s commit hash. Do NOT advance the milestone. - **Build platform**: use Linux x64 (`dotnet cake --target=externals-linux --arch=x64`). Clang is pre-configured via env vars. This also applies to Phase 10 if a native rebuild is needed. - **NEVER run `externals-download`** in this workflow β€” not even for debugging or baseline comparison. Build from source only. @@ -257,6 +371,10 @@ After Phase 10, write these files: cat > /tmp/gh-aw/agent/skia-sync-env.sh << EOF TARGET=${{ needs.pre_activation.outputs.target }} CURRENT=${{ needs.pre_activation.outputs.current }} + IS_RELEASE=${{ needs.pre_activation.outputs.is_release }} + BASE_BRANCH=${{ needs.pre_activation.outputs.base_branch }} + SKIA_BASE_BRANCH=${{ needs.pre_activation.outputs.skia_base_branch }} + HEAD_BRANCH=${{ needs.pre_activation.outputs.head_branch }} EOF ``` @@ -269,5 +387,5 @@ After Phase 10, write these files: All files written to `/tmp/gh-aw/agent/` are automatically uploaded as workflow artifacts. Write test output there too (`/tmp/gh-aw/agent/test-output.txt`) so failures can be inspected after the run. -Commit submodule changes inside `externals/skia` on `skia-sync/m${{ needs.pre_activation.outputs.target }}`. -Commit parent repo changes on `skia-sync/m${{ needs.pre_activation.outputs.target }}` in the parent. +Commit submodule changes inside `externals/skia` on `${{ needs.pre_activation.outputs.head_branch }}`. +Commit parent repo changes on `${{ needs.pre_activation.outputs.head_branch }}` in the parent. From 9b128b8daf39d8f082a94ce7deb46655aaf843c8 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 23 Jun 2026 20:18:29 +0200 Subject: [PATCH 02/11] Extract Skia sync detection into committed scripts Move the milestone/branch-line resolution out of the inline workflow YAML and into two committed, testable scripts: - skia-sync-detect.sh: the single source of truth for mode/target/base-branch resolution. Emits key=value to $SKIA_SYNC_OUT (default $GITHUB_OUTPUT); --gate adds the upstream-exists / already-merged checks. Run by both the pre_activation step (--gate, writes job outputs) and the agent align step (sourced, since that job can't read pre_activation outputs). - skia-sync-align-submodule.sh: the submodule alignment, driven by the sourced base_branch / skia_base_branch. The pre_activation job now sparse-checks-out .github/scripts so it can call the script. This collapses the two large inline bash blocks to ~1 and ~5 lines and removes the duplicated branch-resolution logic. Behaviour is preserved for the main line and the single-match release path. The extraction also folds in two release-resolution fixes from review that were not yet in the previous commit: the strictly-older check is now a numeric compare (`-lt` instead of `!=`), and a milestone matching multiple release branches now hard-fails instead of silently picking the first via `head -1`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/skia-sync-align-submodule.sh | 30 ++ .github/scripts/skia-sync-detect.sh | 156 +++++++++ .github/workflows/auto-skia-sync.lock.yml | 326 ++----------------- .github/workflows/auto-skia-sync.md | 180 +--------- 4 files changed, 239 insertions(+), 453 deletions(-) create mode 100755 .github/scripts/skia-sync-align-submodule.sh create mode 100755 .github/scripts/skia-sync-detect.sh diff --git a/.github/scripts/skia-sync-align-submodule.sh b/.github/scripts/skia-sync-align-submodule.sh new file mode 100755 index 00000000000..8ea0d9f94a7 --- /dev/null +++ b/.github/scripts/skia-sync-align-submodule.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# skia-sync-align-submodule.sh β€” Point externals/skia at the SHA the base branch +# records, before the agent runs. +# +# The agent job checks out the workflow branch (usually main), so the submodule may +# sit at a different SHA than the base branch (`main` or a release line) expects. The +# submodule tracks `$skia_base_branch` in mono/skia (skiasharp for a main sync, +# release/..x for a release sync), so the base-branch submodule SHA +# should be a commit on that branch. +# +# Requires (export these by sourcing skia-sync-detect.sh's output first): +# base_branch parent base branch (main or release/..x) +# skia_base_branch mono/skia base branch (skiasharp or release/..x) + +set -euo pipefail + +BASE_BRANCH="${base_branch:?base_branch not set β€” source skia-sync-detect.sh first}" +SKIA_BASE_BRANCH="${skia_base_branch:?skia_base_branch not set β€” source skia-sync-detect.sh first}" + +echo "Aligning submodule to origin/${BASE_BRANCH} (mono/skia ${SKIA_BASE_BRANCH})" +git fetch origin "$BASE_BRANCH" 2>&1 || true +BASE_SUB_SHA=$(git ls-tree "origin/${BASE_BRANCH}" -- externals/skia | awk '{print $3}') +echo "origin/${BASE_BRANCH} submodule SHA: $BASE_SUB_SHA" +git -C externals/skia fetch origin "$SKIA_BASE_BRANCH" 2>&1 +git -C externals/skia checkout "$BASE_SUB_SHA" 2>&1 +echo "Verifying SHA is on ${SKIA_BASE_BRANCH} branch:" +git -C externals/skia branch -r --contains "$BASE_SUB_SHA" | grep -q "origin/${SKIA_BASE_BRANCH}" \ + && echo " βœ… SHA is on origin/${SKIA_BASE_BRANCH}" \ + || echo " ⚠️ SHA is NOT on origin/${SKIA_BASE_BRANCH} β€” submodule pointer may be stale" diff --git a/.github/scripts/skia-sync-detect.sh b/.github/scripts/skia-sync-detect.sh new file mode 100755 index 00000000000..ba2bf39b422 --- /dev/null +++ b/.github/scripts/skia-sync-detect.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# +# skia-sync-detect.sh β€” Resolve which Skia milestone and branch line the upstream +# sync targets. This is the SINGLE source of truth for branch resolution, shared by: +# +# - the pre_activation "Detect milestone" step β†’ run with --gate; writes the job +# outputs consumed by the prompt and the activation gate. +# - the agent job "Align submodule" step β†’ run without --gate and sourced, +# because that job cannot read pre_activation's outputs and must recompute them. +# +# Contract: +# - `key=value` lines (lowercase, matching the workflow's declared outputs) are +# appended to the file named by $SKIA_SYNC_OUT, defaulting to $GITHUB_OUTPUT. +# - All human-readable logs and ::error::/::notice:: workflow commands go to stdout +# (so GitHub renders annotations and the machine output stays clean for sourcing). +# +# Inputs (env): +# INPUT_MODE github.event.inputs.mode (current|next|latest, optional) +# INPUT_MILESTONE github.event.inputs.milestone (exact number, optional override) +# SCHEDULE github.event.schedule (cron string, optional) +# GITHUB_REPOSITORY owner/repo of this SkiaSharp checkout (default GitHub env) +# GITHUB_REF ref whose scripts/VERSIONS.txt gives main's milestone +# GH_TOKEN token for `gh api` +# +# Flags: +# --gate Also run the gating checks (does upstream exist? is it already merged?) +# and emit `skip=true` / exit accordingly. Only pre_activation needs this. + +set -euo pipefail + +GATE=false +[ "${1:-}" = "--gate" ] && GATE=true + +OUT="${SKIA_SYNC_OUT:-${GITHUB_OUTPUT:?SKIA_SYNC_OUT or GITHUB_OUTPUT must be set}}" +emit() { printf '%s=%s\n' "$1" "$2" >>"$OUT"; } + +# Milestone number from scripts/VERSIONS.txt at the given ref (remote read, no checkout). +milestone_of() { + gh api "repos/${GITHUB_REPOSITORY}/contents/scripts/VERSIONS.txt?ref=$1" \ + --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}' +} + +# -- Mode ------------------------------------------------------------- +if [ -n "${INPUT_MODE:-}" ]; then + MODE="$INPUT_MODE" +elif [ "${SCHEDULE:-}" = "0 7 * * *" ]; then + MODE=current +elif [ "${SCHEDULE:-}" = "0 17 * * *" ]; then + MODE=latest +else + MODE=next +fi + +MAIN_MS=$(milestone_of "$GITHUB_REF") +NEXT=$((MAIN_MS + 1)) +LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ + | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' | sort -n | tail -1) + +# -- Target ----------------------------------------------------------- +if [ -n "${INPUT_MILESTONE:-}" ]; then + TARGET="$INPUT_MILESTONE"; MODE="explicit" +elif [ "$MODE" = latest ]; then + TARGET="$LATEST" +elif [ "$MODE" = current ]; then + TARGET="$MAIN_MS" +else + TARGET="$NEXT" +fi + +# -- Target line: `main` (newest) vs a release/..x maintenance line. +# A maintenance line lives under the SAME branch name in both mono/SkiaSharp and +# mono/skia. We only look for one when TARGET is strictly OLDER than main's milestone; +# the newest line is always served by `main`. The release branch itself is owned by +# the release process (release-branch skill), NOT this sync. +IS_RELEASE=false +BASE_BRANCH=main +SKIA_BASE_BRANCH=skiasharp +HEAD_BRANCH="skia-sync/m${TARGET}" +RELEASE_BRANCH="" +# `2>/dev/null` swallows the "integer expression expected" noise for a non-numeric +# TARGET, which then falls through to the main line. +if [ "$TARGET" -lt "$MAIN_MS" ] 2>/dev/null; then + RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" \ + "refs/heads/release/*.${TARGET}.x" \ + | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | sort -u) + # The glob can match more than one major (e.g. release/4.148.x and a stray + # release/14.148.x). Refuse to guess β€” fail so a human disambiguates. + if [ "$(printf '%s' "$RELEASE_BRANCH" | grep -c . || true)" -gt 1 ]; then + echo "::error::Multiple release branches match milestone ${TARGET}: $(echo $RELEASE_BRANCH) β€” cannot disambiguate." + exit 1 + fi +fi +if [ -n "$RELEASE_BRANCH" ]; then + IS_RELEASE=true + BASE_BRANCH="$RELEASE_BRANCH" + SKIA_BASE_BRANCH="$RELEASE_BRANCH" + HEAD_BRANCH="skia-sync/${RELEASE_BRANCH//\//-}" + # The matching mono/skia release branch MUST already exist. + if [ -z "$(git ls-remote --heads https://github.com/mono/skia.git "refs/heads/${SKIA_BASE_BRANCH}" | awk '{print $1}')" ]; then + echo "::error::mono/skia branch '${SKIA_BASE_BRANCH}' does not exist. Release branches are owned by the release process (release-branch skill) β€” create it before running a release sync for m${TARGET}." + exit 1 + fi +fi + +# `current` = milestone of the BASE branch we sync INTO: +# - main line β†’ main's milestone +# - release β†’ that line's milestone (== TARGET β‡’ a bug-fix-only sync) +if [ "$IS_RELEASE" = true ]; then + CURRENT=$(milestone_of "$BASE_BRANCH") +else + CURRENT="$MAIN_MS" +fi + +emit mode "$MODE" +emit next "$NEXT" +emit latest "$LATEST" +emit target "$TARGET" +emit is_release "$IS_RELEASE" +emit base_branch "$BASE_BRANCH" +emit skia_base_branch "$SKIA_BASE_BRANCH" +emit head_branch "$HEAD_BRANCH" +emit current "$CURRENT" + +echo "Resolved: m${TARGET} β†’ ${BASE_BRANCH} (mode=${MODE}, base milestone=m${CURRENT}, latest=m${LATEST}, head=${HEAD_BRANCH}, release=${IS_RELEASE})" + +# Branch derivation is all the agent job needs; gating is pre_activation-only. +$GATE || exit 0 + +# -- Gate: only spin up the (expensive) agent when there is new upstream work ---- +UPSTREAM_SHA=$(git ls-remote https://github.com/google/skia.git "refs/heads/chrome/m${TARGET}" | awk '{print $1}') +if [ -z "$UPSTREAM_SHA" ]; then + echo "::notice::upstream/chrome/m${TARGET} does not exist yet" + [ "$MODE" = explicit ] && exit 1 + emit skip true + exit 0 +fi + +# Compare upstream HEAD against the existing sync branch if present; otherwise, for a +# release line, against the release base branch. A main milestone bump has no sync +# branch yet and always has work, so it proceeds. +SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/${HEAD_BRANCH}" | awk '{print $1}') +COMPARE_REF="" +if [ -n "$SYNC_SHA" ]; then + COMPARE_REF="$HEAD_BRANCH" +elif [ "$IS_RELEASE" = true ]; then + COMPARE_REF="$SKIA_BASE_BRANCH" +fi +if [ -n "$COMPARE_REF" ]; then + BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...${COMPARE_REF}" --jq '.behind_by' 2>/dev/null || echo unknown) + if [ "$BEHIND" = 0 ]; then + echo "::notice::chrome/m${TARGET} already fully merged into ${COMPARE_REF} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" + emit skip true + exit 0 + fi + echo "${COMPARE_REF} exists but is ${BEHIND} commits behind upstream" +fi diff --git a/.github/workflows/auto-skia-sync.lock.yml b/.github/workflows/auto-skia-sync.lock.yml index 595e0b9afe6..76f2ccca3d6 100644 --- a/.github/workflows/auto-skia-sync.lock.yml +++ b/.github/workflows/auto-skia-sync.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c6bbf9789260e93f5f0c4327813b2def5a3da617e56dff527364a7e3f268a448","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3a223b93d31db74929a5ffb8f1555585301d36f6cbb17c6e31e62f578b585aca","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -36,6 +36,7 @@ # - SKIASHARP_AUTOBUMP_TOKEN # # Custom actions used: +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -58,6 +59,10 @@ name: "Skia Upstream Sync" - cron: "0 12 * * *" - cron: "0 17 * * *" # steps: # Steps injected into pre-activation job + # - name: Check out detection scripts + # uses: actions/checkout@v4 + # with: + # sparse-checkout: .github/scripts # - env: # GH_TOKEN: ${{ github.token }} # INPUT_MILESTONE: ${{ github.event.inputs.milestone }} @@ -65,121 +70,7 @@ name: "Skia Upstream Sync" # SCHEDULE: ${{ github.event.schedule }} # id: detect # name: Detect milestone - # run: | - # if [ -n "$INPUT_MODE" ]; then - # MODE="$INPUT_MODE" - # elif [ "$SCHEDULE" = "0 7 * * *" ]; then - # MODE=current - # elif [ "$SCHEDULE" = "0 17 * * *" ]; then - # MODE=latest - # else - # MODE=next - # fi - # echo "mode=$MODE" >> "$GITHUB_OUTPUT" - # - # # main's milestone β€” the basis for `current`/`next` mode math. - # MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ - # --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - # - # NEXT=$((MAIN_MS + 1)) - # echo "next=$NEXT" >> "$GITHUB_OUTPUT" - # - # LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ - # | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' \ - # | sort -n | tail -1) - # echo "latest=$LATEST" >> "$GITHUB_OUTPUT" - # - # # Explicit milestone input overrides mode - # if [ -n "$INPUT_MILESTONE" ]; then - # TARGET="$INPUT_MILESTONE" - # MODE="explicit" - # elif [ "$MODE" = "latest" ]; then - # TARGET="$LATEST" - # elif [ "$MODE" = "current" ]; then - # TARGET="$MAIN_MS" - # else - # TARGET="$NEXT" - # fi - # echo "target=$TARGET" >> "$GITHUB_OUTPUT" - # - # # -- Resolve the target line: `main` (newest, in-development) or a release line. - # # A maintenance line lives as `release/..x` in BOTH mono/SkiaSharp - # # and mono/skia. We only look for one when TARGET is older than main's milestone β€” - # # the newest line is always served by `main`. The release branch itself is created - # # by the release process (release-branch skill), NOT this sync; if the mono/skia - # # side is missing we fail so branch ownership stays with the release process. - # IS_RELEASE=false - # BASE_BRANCH=main - # SKIA_BASE_BRANCH=skiasharp - # HEAD_BRANCH="skia-sync/m${TARGET}" - # RELEASE_BRANCH="" - # if [ "$TARGET" != "$MAIN_MS" ]; then - # RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ - # | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) - # fi - # if [ -n "$RELEASE_BRANCH" ]; then - # IS_RELEASE=true - # BASE_BRANCH="$RELEASE_BRANCH" - # SKIA_BASE_BRANCH="$RELEASE_BRANCH" - # HEAD_BRANCH="skia-sync/${RELEASE_BRANCH//\//-}" - # - # # The matching mono/skia release branch MUST already exist. - # SKIA_BASE_SHA=$(git ls-remote --heads https://github.com/mono/skia.git "refs/heads/${SKIA_BASE_BRANCH}" | awk '{print $1}') - # if [ -z "$SKIA_BASE_SHA" ]; then - # echo "::error::mono/skia branch '${SKIA_BASE_BRANCH}' does not exist. Release branches are owned by the release process (release-branch skill) β€” create it before running a release sync for m${TARGET}." - # exit 1 - # fi - # fi - # echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT" - # echo "base_branch=$BASE_BRANCH" >> "$GITHUB_OUTPUT" - # echo "skia_base_branch=$SKIA_BASE_BRANCH" >> "$GITHUB_OUTPUT" - # echo "head_branch=$HEAD_BRANCH" >> "$GITHUB_OUTPUT" - # - # # `current` is the milestone of the BASE branch we're syncing INTO: - # # - main line β†’ main's milestone - # # - release β†’ that line's milestone (== TARGET β‡’ a bug-fix-only sync) - # if [ "$IS_RELEASE" = true ]; then - # CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=${BASE_BRANCH}" \ - # --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - # else - # CURRENT="$MAIN_MS" - # fi - # echo "current=$CURRENT" >> "$GITHUB_OUTPUT" - # - # UPSTREAM_SHA=$(git ls-remote https://github.com/google/skia.git "refs/heads/chrome/m${TARGET}" | awk '{print $1}') - # if [ -z "$UPSTREAM_SHA" ]; then - # echo "::notice::upstream/chrome/m${TARGET} does not exist yet" - # if [ "$MODE" = "explicit" ]; then - # exit 1 - # fi - # echo "skip=true" >> "$GITHUB_OUTPUT" - # exit 0 - # fi - # - # # Check whether there is anything new to merge from upstream. Compare upstream - # # HEAD against the existing sync branch if present; otherwise, for release lines, - # # against the release base branch. This avoids spinning up the expensive agent - # # job when there's nothing new. (For a main milestone bump the sync branch won't - # # exist yet and there is always new work, so we proceed.) - # SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/${HEAD_BRANCH}" | awk '{print $1}') - # COMPARE_REF="" - # if [ -n "$SYNC_SHA" ]; then - # COMPARE_REF="$HEAD_BRANCH" - # elif [ "$IS_RELEASE" = true ]; then - # COMPARE_REF="$SKIA_BASE_BRANCH" - # fi - # if [ -n "$COMPARE_REF" ]; then - # BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...${COMPARE_REF}" \ - # --jq '.behind_by' 2>/dev/null || echo "unknown") - # if [ "$BEHIND" = "0" ]; then - # echo "::notice::chrome/m${TARGET} already fully merged into ${COMPARE_REF} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" - # echo "skip=true" >> "$GITHUB_OUTPUT" - # exit 0 - # fi - # echo "${COMPARE_REF} exists but is ${BEHIND} commits behind upstream" - # fi - # - # echo "Will process: m${TARGET} β†’ ${BASE_BRANCH} (mode=${MODE}, base milestone=m${CURRENT}, latest=m${LATEST}, head=${HEAD_BRANCH})" + # run: bash .github/scripts/skia-sync-detect.sh --gate workflow_dispatch: inputs: aw_context: @@ -340,23 +231,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' + cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' - GH_AW_PROMPT_a175c22ffc6eacbc_EOF + GH_AW_PROMPT_edc8624e8bd34a95_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' + cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_a175c22ffc6eacbc_EOF + GH_AW_PROMPT_edc8624e8bd34a95_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' + cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' - GH_AW_PROMPT_a175c22ffc6eacbc_EOF + GH_AW_PROMPT_edc8624e8bd34a95_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' + cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -388,12 +279,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_a175c22ffc6eacbc_EOF + GH_AW_PROMPT_edc8624e8bd34a95_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_a175c22ffc6eacbc_EOF' + cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' {{#runtime-import .github/workflows/auto-skia-sync.md}} - GH_AW_PROMPT_a175c22ffc6eacbc_EOF + GH_AW_PROMPT_edc8624e8bd34a95_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -563,51 +454,14 @@ jobs: NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt - name: Align submodule to the base branch run: | - # Re-derive the sync base branch here (same rules as the pre-activation - # `Detect milestone` step, which stays the source of truth). We can't read - # its outputs: this step runs in the agent job, which only `needs:` - # activation, and the cheap pre-activation job has no checkout to share a - # script from. github.event inputs ARE available in any job, so we redo the - # small base-branch resolution from them. - if [ -n "$INPUT_MODE" ]; then MODE="$INPUT_MODE"; - elif [ "$SCHEDULE" = "0 7 * * *" ]; then MODE=current; - elif [ "$SCHEDULE" = "0 17 * * *" ]; then MODE=latest; - else MODE=next; fi - MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ - --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - if [ -n "$INPUT_MILESTONE" ]; then TARGET="$INPUT_MILESTONE"; - elif [ "$MODE" = "latest" ]; then - TARGET=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ - | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' | sort -n | tail -1); - elif [ "$MODE" = "current" ]; then TARGET="$MAIN_MS"; - else TARGET="$((MAIN_MS + 1))"; fi - - BASE_BRANCH=main - SKIA_BASE_BRANCH=skiasharp - if [ "$TARGET" != "$MAIN_MS" ]; then - RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ - | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) - if [ -n "$RELEASE_BRANCH" ]; then - BASE_BRANCH="$RELEASE_BRANCH" - SKIA_BASE_BRANCH="$RELEASE_BRANCH" - fi - fi - - # The checkout uses the workflow branch (main), so the submodule may be at a - # different SHA than the base branch expects. Fix it before the agent runs. - # The submodule tracks `${SKIA_BASE_BRANCH}` in mono/skia (skiasharp for a - # main sync, release/..x for a release sync), so the - # base-branch submodule SHA should be a commit on that branch. - echo "Aligning submodule to origin/${BASE_BRANCH} (mono/skia ${SKIA_BASE_BRANCH})" - git fetch origin "$BASE_BRANCH" 2>&1 || true - BASE_SUB_SHA=$(git ls-tree "origin/${BASE_BRANCH}" -- externals/skia | awk '{print $3}') - echo "origin/${BASE_BRANCH} submodule SHA: $BASE_SUB_SHA" - git -C externals/skia fetch origin "$SKIA_BASE_BRANCH" 2>&1 - git -C externals/skia checkout "$BASE_SUB_SHA" 2>&1 - echo "Verifying SHA is on ${SKIA_BASE_BRANCH} branch:" - git -C externals/skia branch -r --contains "$BASE_SUB_SHA" | grep -q "origin/${SKIA_BASE_BRANCH}" \ - && echo " βœ… SHA is on origin/${SKIA_BASE_BRANCH}" \ - || echo " ⚠️ SHA is NOT on origin/${SKIA_BASE_BRANCH} β€” submodule pointer may be stale" + # The agent job can't read pre_activation's outputs (it only `needs:` + # activation), so re-run the same committed detector to recover base_branch / + # skia_base_branch, then align the submodule. skia-sync-detect.sh is the single + # source of truth β€” no branch logic is duplicated here. + OUT=$(mktemp) + SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh + set -a; . "$OUT"; set +a + bash .github/scripts/skia-sync-align-submodule.sh env: GH_HOST: localhost:18443 GH_REPO: ${{ github.repository }} @@ -697,9 +551,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_9b1865f00eeb8608_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f0e0572f06c64611_EOF' {"create_issue":{"labels":["auto-skia-sync"],"max":1,"title_prefix":"[auto-skia-sync]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_9b1865f00eeb8608_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_f0e0572f06c64611_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -897,7 +751,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_22666b6fb299eac2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_dedffa87e7764a75_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -945,7 +799,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_22666b6fb299eac2_EOF + GH_AW_MCP_CONFIG_dedffa87e7764a75_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1541,123 +1395,13 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); await main(); + - name: Check out detection scripts + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + sparse-checkout: .github/scripts - name: Detect milestone id: detect - run: | - if [ -n "$INPUT_MODE" ]; then - MODE="$INPUT_MODE" - elif [ "$SCHEDULE" = "0 7 * * *" ]; then - MODE=current - elif [ "$SCHEDULE" = "0 17 * * *" ]; then - MODE=latest - else - MODE=next - fi - echo "mode=$MODE" >> "$GITHUB_OUTPUT" - - # main's milestone β€” the basis for `current`/`next` mode math. - MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ - --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - - NEXT=$((MAIN_MS + 1)) - echo "next=$NEXT" >> "$GITHUB_OUTPUT" - - LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ - | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' \ - | sort -n | tail -1) - echo "latest=$LATEST" >> "$GITHUB_OUTPUT" - - # Explicit milestone input overrides mode - if [ -n "$INPUT_MILESTONE" ]; then - TARGET="$INPUT_MILESTONE" - MODE="explicit" - elif [ "$MODE" = "latest" ]; then - TARGET="$LATEST" - elif [ "$MODE" = "current" ]; then - TARGET="$MAIN_MS" - else - TARGET="$NEXT" - fi - echo "target=$TARGET" >> "$GITHUB_OUTPUT" - - # -- Resolve the target line: `main` (newest, in-development) or a release line. - # A maintenance line lives as `release/..x` in BOTH mono/SkiaSharp - # and mono/skia. We only look for one when TARGET is older than main's milestone β€” - # the newest line is always served by `main`. The release branch itself is created - # by the release process (release-branch skill), NOT this sync; if the mono/skia - # side is missing we fail so branch ownership stays with the release process. - IS_RELEASE=false - BASE_BRANCH=main - SKIA_BASE_BRANCH=skiasharp - HEAD_BRANCH="skia-sync/m${TARGET}" - RELEASE_BRANCH="" - if [ "$TARGET" != "$MAIN_MS" ]; then - RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ - | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) - fi - if [ -n "$RELEASE_BRANCH" ]; then - IS_RELEASE=true - BASE_BRANCH="$RELEASE_BRANCH" - SKIA_BASE_BRANCH="$RELEASE_BRANCH" - HEAD_BRANCH="skia-sync/${RELEASE_BRANCH//\//-}" - - # The matching mono/skia release branch MUST already exist. - SKIA_BASE_SHA=$(git ls-remote --heads https://github.com/mono/skia.git "refs/heads/${SKIA_BASE_BRANCH}" | awk '{print $1}') - if [ -z "$SKIA_BASE_SHA" ]; then - echo "::error::mono/skia branch '${SKIA_BASE_BRANCH}' does not exist. Release branches are owned by the release process (release-branch skill) β€” create it before running a release sync for m${TARGET}." - exit 1 - fi - fi - echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT" - echo "base_branch=$BASE_BRANCH" >> "$GITHUB_OUTPUT" - echo "skia_base_branch=$SKIA_BASE_BRANCH" >> "$GITHUB_OUTPUT" - echo "head_branch=$HEAD_BRANCH" >> "$GITHUB_OUTPUT" - - # `current` is the milestone of the BASE branch we're syncing INTO: - # - main line β†’ main's milestone - # - release β†’ that line's milestone (== TARGET β‡’ a bug-fix-only sync) - if [ "$IS_RELEASE" = true ]; then - CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=${BASE_BRANCH}" \ - --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - else - CURRENT="$MAIN_MS" - fi - echo "current=$CURRENT" >> "$GITHUB_OUTPUT" - - UPSTREAM_SHA=$(git ls-remote https://github.com/google/skia.git "refs/heads/chrome/m${TARGET}" | awk '{print $1}') - if [ -z "$UPSTREAM_SHA" ]; then - echo "::notice::upstream/chrome/m${TARGET} does not exist yet" - if [ "$MODE" = "explicit" ]; then - exit 1 - fi - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Check whether there is anything new to merge from upstream. Compare upstream - # HEAD against the existing sync branch if present; otherwise, for release lines, - # against the release base branch. This avoids spinning up the expensive agent - # job when there's nothing new. (For a main milestone bump the sync branch won't - # exist yet and there is always new work, so we proceed.) - SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/${HEAD_BRANCH}" | awk '{print $1}') - COMPARE_REF="" - if [ -n "$SYNC_SHA" ]; then - COMPARE_REF="$HEAD_BRANCH" - elif [ "$IS_RELEASE" = true ]; then - COMPARE_REF="$SKIA_BASE_BRANCH" - fi - if [ -n "$COMPARE_REF" ]; then - BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...${COMPARE_REF}" \ - --jq '.behind_by' 2>/dev/null || echo "unknown") - if [ "$BEHIND" = "0" ]; then - echo "::notice::chrome/m${TARGET} already fully merged into ${COMPARE_REF} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - echo "${COMPARE_REF} exists but is ${BEHIND} commits behind upstream" - fi - - echo "Will process: m${TARGET} β†’ ${BASE_BRANCH} (mode=${MODE}, base milestone=m${CURRENT}, latest=m${LATEST}, head=${HEAD_BRANCH})" + run: bash .github/scripts/skia-sync-detect.sh --gate env: GH_TOKEN: ${{ github.token }} INPUT_MILESTONE: ${{ github.event.inputs.milestone }} diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 54b967aaa05..badce678a6d 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -23,11 +23,18 @@ on: type: string # -- Pre-activation step ------------------------------------------- - # Runs BEFORE the agent job. Detects the target milestone. - # Exit 1 = hard failure (explicit milestone input doesn't exist). + # Runs BEFORE the agent job. Detects the target milestone + branch line. + # All resolution logic lives in the committed .github/scripts/skia-sync-detect.sh + # (the single source of truth, sparse-checked-out below); --gate adds the + # "is there anything to sync?" check. + # Exit 1 = hard failure (explicit milestone input doesn't exist / branch missing). # skip=true output = nothing to sync (graceful skip, workflow shows green). # Outputs are available in the prompt via ${{ needs.pre_activation.outputs.* }}. steps: + - name: Check out detection scripts + uses: actions/checkout@v4 + with: + sparse-checkout: .github/scripts - name: Detect milestone id: detect env: @@ -35,121 +42,7 @@ on: INPUT_MILESTONE: ${{ github.event.inputs.milestone }} SCHEDULE: ${{ github.event.schedule }} GH_TOKEN: ${{ github.token }} - run: | - if [ -n "$INPUT_MODE" ]; then - MODE="$INPUT_MODE" - elif [ "$SCHEDULE" = "0 7 * * *" ]; then - MODE=current - elif [ "$SCHEDULE" = "0 17 * * *" ]; then - MODE=latest - else - MODE=next - fi - echo "mode=$MODE" >> "$GITHUB_OUTPUT" - - # main's milestone β€” the basis for `current`/`next` mode math. - MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ - --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - - NEXT=$((MAIN_MS + 1)) - echo "next=$NEXT" >> "$GITHUB_OUTPUT" - - LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ - | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' \ - | sort -n | tail -1) - echo "latest=$LATEST" >> "$GITHUB_OUTPUT" - - # Explicit milestone input overrides mode - if [ -n "$INPUT_MILESTONE" ]; then - TARGET="$INPUT_MILESTONE" - MODE="explicit" - elif [ "$MODE" = "latest" ]; then - TARGET="$LATEST" - elif [ "$MODE" = "current" ]; then - TARGET="$MAIN_MS" - else - TARGET="$NEXT" - fi - echo "target=$TARGET" >> "$GITHUB_OUTPUT" - - # -- Resolve the target line: `main` (newest, in-development) or a release line. - # A maintenance line lives as `release/..x` in BOTH mono/SkiaSharp - # and mono/skia. We only look for one when TARGET is older than main's milestone β€” - # the newest line is always served by `main`. The release branch itself is created - # by the release process (release-branch skill), NOT this sync; if the mono/skia - # side is missing we fail so branch ownership stays with the release process. - IS_RELEASE=false - BASE_BRANCH=main - SKIA_BASE_BRANCH=skiasharp - HEAD_BRANCH="skia-sync/m${TARGET}" - RELEASE_BRANCH="" - if [ "$TARGET" != "$MAIN_MS" ]; then - RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ - | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) - fi - if [ -n "$RELEASE_BRANCH" ]; then - IS_RELEASE=true - BASE_BRANCH="$RELEASE_BRANCH" - SKIA_BASE_BRANCH="$RELEASE_BRANCH" - HEAD_BRANCH="skia-sync/${RELEASE_BRANCH//\//-}" - - # The matching mono/skia release branch MUST already exist. - SKIA_BASE_SHA=$(git ls-remote --heads https://github.com/mono/skia.git "refs/heads/${SKIA_BASE_BRANCH}" | awk '{print $1}') - if [ -z "$SKIA_BASE_SHA" ]; then - echo "::error::mono/skia branch '${SKIA_BASE_BRANCH}' does not exist. Release branches are owned by the release process (release-branch skill) β€” create it before running a release sync for m${TARGET}." - exit 1 - fi - fi - echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT" - echo "base_branch=$BASE_BRANCH" >> "$GITHUB_OUTPUT" - echo "skia_base_branch=$SKIA_BASE_BRANCH" >> "$GITHUB_OUTPUT" - echo "head_branch=$HEAD_BRANCH" >> "$GITHUB_OUTPUT" - - # `current` is the milestone of the BASE branch we're syncing INTO: - # - main line β†’ main's milestone - # - release β†’ that line's milestone (== TARGET β‡’ a bug-fix-only sync) - if [ "$IS_RELEASE" = true ]; then - CURRENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=${BASE_BRANCH}" \ - --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - else - CURRENT="$MAIN_MS" - fi - echo "current=$CURRENT" >> "$GITHUB_OUTPUT" - - UPSTREAM_SHA=$(git ls-remote https://github.com/google/skia.git "refs/heads/chrome/m${TARGET}" | awk '{print $1}') - if [ -z "$UPSTREAM_SHA" ]; then - echo "::notice::upstream/chrome/m${TARGET} does not exist yet" - if [ "$MODE" = "explicit" ]; then - exit 1 - fi - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Check whether there is anything new to merge from upstream. Compare upstream - # HEAD against the existing sync branch if present; otherwise, for release lines, - # against the release base branch. This avoids spinning up the expensive agent - # job when there's nothing new. (For a main milestone bump the sync branch won't - # exist yet and there is always new work, so we proceed.) - SYNC_SHA=$(git ls-remote https://github.com/mono/skia.git "refs/heads/${HEAD_BRANCH}" | awk '{print $1}') - COMPARE_REF="" - if [ -n "$SYNC_SHA" ]; then - COMPARE_REF="$HEAD_BRANCH" - elif [ "$IS_RELEASE" = true ]; then - COMPARE_REF="$SKIA_BASE_BRANCH" - fi - if [ -n "$COMPARE_REF" ]; then - BEHIND=$(gh api "repos/mono/skia/compare/${UPSTREAM_SHA}...${COMPARE_REF}" \ - --jq '.behind_by' 2>/dev/null || echo "unknown") - if [ "$BEHIND" = "0" ]; then - echo "::notice::chrome/m${TARGET} already fully merged into ${COMPARE_REF} (upstream HEAD: ${UPSTREAM_SHA:0:12}) β€” skipping" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - echo "${COMPARE_REF} exists but is ${BEHIND} commits behind upstream" - fi - - echo "Will process: m${TARGET} β†’ ${BASE_BRANCH} (mode=${MODE}, base milestone=m${CURRENT}, latest=m${LATEST}, head=${HEAD_BRANCH})" + run: bash .github/scripts/skia-sync-detect.sh --gate # -- Pre-activation outputs ------------------------------------------ # Expose detect step outputs for use in the prompt and other jobs. @@ -245,51 +138,14 @@ steps: SCHEDULE: ${{ github.event.schedule }} GH_TOKEN: ${{ github.token }} run: | - # Re-derive the sync base branch here (same rules as the pre-activation - # `Detect milestone` step, which stays the source of truth). We can't read - # its outputs: this step runs in the agent job, which only `needs:` - # activation, and the cheap pre-activation job has no checkout to share a - # script from. github.event inputs ARE available in any job, so we redo the - # small base-branch resolution from them. - if [ -n "$INPUT_MODE" ]; then MODE="$INPUT_MODE"; - elif [ "$SCHEDULE" = "0 7 * * *" ]; then MODE=current; - elif [ "$SCHEDULE" = "0 17 * * *" ]; then MODE=latest; - else MODE=next; fi - MAIN_MS=$(gh api "repos/$GITHUB_REPOSITORY/contents/scripts/VERSIONS.txt?ref=$GITHUB_REF" \ - --jq '.content' | base64 -d | grep '^libSkiaSharp.*milestone' | awk '{print $NF}') - if [ -n "$INPUT_MILESTONE" ]; then TARGET="$INPUT_MILESTONE"; - elif [ "$MODE" = "latest" ]; then - TARGET=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ - | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' | sort -n | tail -1); - elif [ "$MODE" = "current" ]; then TARGET="$MAIN_MS"; - else TARGET="$((MAIN_MS + 1))"; fi - - BASE_BRANCH=main - SKIA_BASE_BRANCH=skiasharp - if [ "$TARGET" != "$MAIN_MS" ]; then - RELEASE_BRANCH=$(git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" "refs/heads/release/*.${TARGET}.x" \ - | sed -n 's|.*refs/heads/\(release/[0-9][0-9.]*\.x\)$|\1|p' | head -1) - if [ -n "$RELEASE_BRANCH" ]; then - BASE_BRANCH="$RELEASE_BRANCH" - SKIA_BASE_BRANCH="$RELEASE_BRANCH" - fi - fi - - # The checkout uses the workflow branch (main), so the submodule may be at a - # different SHA than the base branch expects. Fix it before the agent runs. - # The submodule tracks `${SKIA_BASE_BRANCH}` in mono/skia (skiasharp for a - # main sync, release/..x for a release sync), so the - # base-branch submodule SHA should be a commit on that branch. - echo "Aligning submodule to origin/${BASE_BRANCH} (mono/skia ${SKIA_BASE_BRANCH})" - git fetch origin "$BASE_BRANCH" 2>&1 || true - BASE_SUB_SHA=$(git ls-tree "origin/${BASE_BRANCH}" -- externals/skia | awk '{print $3}') - echo "origin/${BASE_BRANCH} submodule SHA: $BASE_SUB_SHA" - git -C externals/skia fetch origin "$SKIA_BASE_BRANCH" 2>&1 - git -C externals/skia checkout "$BASE_SUB_SHA" 2>&1 - echo "Verifying SHA is on ${SKIA_BASE_BRANCH} branch:" - git -C externals/skia branch -r --contains "$BASE_SUB_SHA" | grep -q "origin/${SKIA_BASE_BRANCH}" \ - && echo " βœ… SHA is on origin/${SKIA_BASE_BRANCH}" \ - || echo " ⚠️ SHA is NOT on origin/${SKIA_BASE_BRANCH} β€” submodule pointer may be stale" + # The agent job can't read pre_activation's outputs (it only `needs:` + # activation), so re-run the same committed detector to recover base_branch / + # skia_base_branch, then align the submodule. skia-sync-detect.sh is the single + # source of truth β€” no branch logic is duplicated here. + OUT=$(mktemp) + SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh + set -a; . "$OUT"; set +a + bash .github/scripts/skia-sync-align-submodule.sh - name: Copy push script for post-step run: | cp .github/scripts/skia-sync-push-prs.sh /tmp/gh-aw/skia-sync-push-prs.sh From 68a2087f8f6db6943e343d1cb6e525e5831c4aa8 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 22:03:47 +0200 Subject: [PATCH 03/11] Use Claude Opus for the Skia upstream sync workflow Add an engine: block pinning model claude-opus-4.6 (overriding the default claude-sonnet-4.6). Upstream merge/conflict resolution and native build-fix reasoning are hard, so the stronger model is worth the higher AI-credit cost for this workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-skia-sync.lock.yml | 42 ++++++++++------------- .github/workflows/auto-skia-sync.md | 16 ++++++--- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/.github/workflows/auto-skia-sync.lock.yml b/.github/workflows/auto-skia-sync.lock.yml index 76f2ccca3d6..70d227cafe7 100644 --- a/.github/workflows/auto-skia-sync.lock.yml +++ b/.github/workflows/auto-skia-sync.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3a223b93d31db74929a5ffb8f1555585301d36f6cbb17c6e31e62f578b585aca","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f08639f8efeb309cdf7cdec60cf594d07117d1c5f9e7b493d0e1c48f0f8bc964","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -140,7 +140,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_MODEL: "claude-opus-4.6" GH_AW_INFO_VERSION: "1.0.40" GH_AW_INFO_AGENT_VERSION: "1.0.40" GH_AW_INFO_CLI_VERSION: "v0.71.5" @@ -231,23 +231,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' + cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' - GH_AW_PROMPT_edc8624e8bd34a95_EOF + GH_AW_PROMPT_b5007c09cb1edeb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' + cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_edc8624e8bd34a95_EOF + GH_AW_PROMPT_b5007c09cb1edeb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' + cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' - GH_AW_PROMPT_edc8624e8bd34a95_EOF + GH_AW_PROMPT_b5007c09cb1edeb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' + cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -279,12 +279,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_edc8624e8bd34a95_EOF + GH_AW_PROMPT_b5007c09cb1edeb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_edc8624e8bd34a95_EOF' + cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' {{#runtime-import .github/workflows/auto-skia-sync.md}} - GH_AW_PROMPT_edc8624e8bd34a95_EOF + GH_AW_PROMPT_b5007c09cb1edeb9_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -454,10 +454,6 @@ jobs: NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt - name: Align submodule to the base branch run: | - # The agent job can't read pre_activation's outputs (it only `needs:` - # activation), so re-run the same committed detector to recover base_branch / - # skia_base_branch, then align the submodule. skia-sync-detect.sh is the single - # source of truth β€” no branch logic is duplicated here. OUT=$(mktemp) SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh set -a; . "$OUT"; set +a @@ -551,9 +547,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f0e0572f06c64611_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4f6b9850ef01f656_EOF' {"create_issue":{"labels":["auto-skia-sync"],"max":1,"title_prefix":"[auto-skia-sync]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_f0e0572f06c64611_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_4f6b9850ef01f656_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -751,7 +747,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_dedffa87e7764a75_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_41024a5bd69a6a88_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -799,7 +795,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_dedffa87e7764a75_EOF + GH_AW_MCP_CONFIG_41024a5bd69a6a88_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -839,7 +835,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_MODEL: claude-opus-4.6 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -1300,7 +1296,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_MODEL: claude-opus-4.6 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_VERSION: v0.71.5 @@ -1423,7 +1419,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_MODEL: "claude-opus-4.6" GH_AW_ENGINE_VERSION: "1.0.40" GH_AW_SAFE_OUTPUTS_STAGED: "true" GH_AW_WORKFLOW_ID: "auto-skia-sync" diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index badce678a6d..240a0ec700d 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -1,6 +1,14 @@ --- description: "Daily upstream Skia milestone sync - merges new commits, resolves conflicts, builds, tests, and creates PRs." +# -- Engine ------------------------------------------------------------ +# Use Claude Opus (instead of the default Sonnet) for this workflow: the +# upstream merge/conflict-resolution and build-fix reasoning is hard and +# benefits from the stronger model, despite the higher AI-credit cost. +engine: + id: copilot + model: claude-opus-4.6 + # -- Triggers ---------------------------------------------------------- # Three daily crons: current (7 AM), next (12 PM), latest (5 PM UTC). # Manual dispatch with mode selector. @@ -137,11 +145,11 @@ steps: INPUT_MILESTONE: ${{ github.event.inputs.milestone }} SCHEDULE: ${{ github.event.schedule }} GH_TOKEN: ${{ github.token }} + # The agent job can't read pre_activation's outputs (it only `needs:` + # activation), so re-run the same committed detector to recover base_branch / + # skia_base_branch, then align the submodule. skia-sync-detect.sh is the single + # source of truth β€” no branch logic is duplicated here. run: | - # The agent job can't read pre_activation's outputs (it only `needs:` - # activation), so re-run the same committed detector to recover base_branch / - # skia_base_branch, then align the submodule. skia-sync-detect.sh is the single - # source of truth β€” no branch logic is duplicated here. OUT=$(mktemp) SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh set -a; . "$OUT"; set +a From 30c0110e38e78c17fde53c356089cc0fd86ec6dc Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 23:40:36 +0200 Subject: [PATCH 04/11] Bump Skia sync workflow model to Claude Opus 4.7 Opus 4.7 carries the same 5x AI-credit multiplier as 4.6, so move to the newer model for the upstream merge/conflict-resolution reasoning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-skia-sync.lock.yml | 38 +++++++++++------------ .github/workflows/auto-skia-sync.md | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/auto-skia-sync.lock.yml b/.github/workflows/auto-skia-sync.lock.yml index 70d227cafe7..a5f197c26c0 100644 --- a/.github/workflows/auto-skia-sync.lock.yml +++ b/.github/workflows/auto-skia-sync.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f08639f8efeb309cdf7cdec60cf594d07117d1c5f9e7b493d0e1c48f0f8bc964","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"23520ea9dfe94887804849d1637be2d607317b1b23d8364b0262cee491d4d6b9","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -140,7 +140,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: "claude-opus-4.6" + GH_AW_INFO_MODEL: "claude-opus-4.7" GH_AW_INFO_VERSION: "1.0.40" GH_AW_INFO_AGENT_VERSION: "1.0.40" GH_AW_INFO_CLI_VERSION: "v0.71.5" @@ -231,23 +231,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' + cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' - GH_AW_PROMPT_b5007c09cb1edeb9_EOF + GH_AW_PROMPT_318360b86911c4d8_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' + cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_b5007c09cb1edeb9_EOF + GH_AW_PROMPT_318360b86911c4d8_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' + cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' - GH_AW_PROMPT_b5007c09cb1edeb9_EOF + GH_AW_PROMPT_318360b86911c4d8_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' + cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -279,12 +279,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_b5007c09cb1edeb9_EOF + GH_AW_PROMPT_318360b86911c4d8_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_b5007c09cb1edeb9_EOF' + cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' {{#runtime-import .github/workflows/auto-skia-sync.md}} - GH_AW_PROMPT_b5007c09cb1edeb9_EOF + GH_AW_PROMPT_318360b86911c4d8_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -547,9 +547,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4f6b9850ef01f656_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a35deaa2484fa3b2_EOF' {"create_issue":{"labels":["auto-skia-sync"],"max":1,"title_prefix":"[auto-skia-sync]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_4f6b9850ef01f656_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_a35deaa2484fa3b2_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -747,7 +747,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_41024a5bd69a6a88_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_2c5d7a4ccfdfe64e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -795,7 +795,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_41024a5bd69a6a88_EOF + GH_AW_MCP_CONFIG_2c5d7a4ccfdfe64e_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -835,7 +835,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: claude-opus-4.6 + COPILOT_MODEL: claude-opus-4.7 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -1296,7 +1296,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: claude-opus-4.6 + COPILOT_MODEL: claude-opus-4.7 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_VERSION: v0.71.5 @@ -1419,7 +1419,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "claude-opus-4.6" + GH_AW_ENGINE_MODEL: "claude-opus-4.7" GH_AW_ENGINE_VERSION: "1.0.40" GH_AW_SAFE_OUTPUTS_STAGED: "true" GH_AW_WORKFLOW_ID: "auto-skia-sync" diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 240a0ec700d..12d60e4d4f2 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -7,7 +7,7 @@ description: "Daily upstream Skia milestone sync - merges new commits, resolves # benefits from the stronger model, despite the higher AI-credit cost. engine: id: copilot - model: claude-opus-4.6 + model: claude-opus-4.7 # -- Triggers ---------------------------------------------------------- # Three daily crons: current (7 AM), next (12 PM), latest (5 PM UTC). From 6baecd4d20daa2e25a191840f7d962c0a64508a1 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 00:09:58 +0200 Subject: [PATCH 05/11] Install libc++ for the Skia sync native Linux build native/linux/build.cake builds libSkiaSharp/libHarfBuzzSharp with -stdlib=libc++. On the real CI that runtime comes from the .NET cross-compilation image, but this workflow builds on a bare Ubuntu host where the agent cannot apt-install anything (no apt/sudo in the AWF chroot, and the firewall blocks the OS package mirrors). Install libc++-dev + libc++abi-dev in the host pre-agent step so the unmodified native build compiles, and tell the agent not to patch native build files or compiler flags to work around a missing dependency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-skia-sync.lock.yml | 32 +++++++++++------------ .github/workflows/auto-skia-sync.md | 19 +++++++++++++- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/.github/workflows/auto-skia-sync.lock.yml b/.github/workflows/auto-skia-sync.lock.yml index a5f197c26c0..1f10eccd233 100644 --- a/.github/workflows/auto-skia-sync.lock.yml +++ b/.github/workflows/auto-skia-sync.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"23520ea9dfe94887804849d1637be2d607317b1b23d8364b0262cee491d4d6b9","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f837ac9c789c59020f6a17a866aa3a43e2086a6421e464b3bf1f406c03a15937","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -231,23 +231,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' + cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' - GH_AW_PROMPT_318360b86911c4d8_EOF + GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' + cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_318360b86911c4d8_EOF + GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' + cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' - GH_AW_PROMPT_318360b86911c4d8_EOF + GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' + cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -279,12 +279,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_318360b86911c4d8_EOF + GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_318360b86911c4d8_EOF' + cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' {{#runtime-import .github/workflows/auto-skia-sync.md}} - GH_AW_PROMPT_318360b86911c4d8_EOF + GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -536,7 +536,7 @@ jobs: name: Install native build dependencies run: |- sudo apt-get update -qq - sudo apt-get install -y clang fontconfig libfontconfig1-dev ninja-build fonts-dejavu-core ttf-ancient-fonts + sudo apt-get install -y clang libc++-dev libc++abi-dev fontconfig libfontconfig1-dev ninja-build fonts-dejavu-core ttf-ancient-fonts fc-cache -f dotnet workload install android --skip-sign-check @@ -547,9 +547,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a35deaa2484fa3b2_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_23016918b95809af_EOF' {"create_issue":{"labels":["auto-skia-sync"],"max":1,"title_prefix":"[auto-skia-sync]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_a35deaa2484fa3b2_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_23016918b95809af_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -747,7 +747,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_2c5d7a4ccfdfe64e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_81e9d30c8bfb6908_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -795,7 +795,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_2c5d7a4ccfdfe64e_EOF + GH_AW_MCP_CONFIG_81e9d30c8bfb6908_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 12d60e4d4f2..855470b9062 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -164,9 +164,19 @@ steps: # carry into the chroot β€” the agent must run it itself. pre-agent-steps: - name: Install native build dependencies + # These run on the HOST and are visible to the agent's AWF chroot via the shared + # filesystem. The agent itself CANNOT apt-install anything (no apt inside the chroot, + # and the firewall blocks the Ubuntu archives), so every native build dependency must + # be installed here. + # + # libc++: native/linux/build.cake builds libSkiaSharp/libHarfBuzzSharp with + # `-stdlib=libc++` (clang's LLVM C++ runtime). On the real CI this comes from the + # .NET cross-compilation image; for this host build we must install libc++-dev + + # libc++abi-dev or the compile fails with "cannot find ". Keep this in + # sync with native/linux/build.cake's extra_cflags/extra_ldflags. run: | sudo apt-get update -qq - sudo apt-get install -y clang fontconfig libfontconfig1-dev ninja-build fonts-dejavu-core ttf-ancient-fonts + sudo apt-get install -y clang libc++-dev libc++abi-dev fontconfig libfontconfig1-dev ninja-build fonts-dejavu-core ttf-ancient-fonts fc-cache -f dotnet workload install android --skip-sign-check env: @@ -219,6 +229,13 @@ Release-line sync: `${{ needs.pre_activation.outputs.is_release }}`. parent-repo change is `cgmanifest.json`'s commit hash. Do NOT advance the milestone. - **Build platform**: use Linux x64 (`dotnet cake --target=externals-linux --arch=x64`). Clang is pre-configured via env vars. This also applies to Phase 10 if a native rebuild is needed. +- **Native build environment is fully provisioned by the workflow** (clang, `libc++-dev`/`libc++abi-dev`, + fontconfig, ninja). Do NOT modify any native build files (`native/**/build.cake`, `scripts/infra/native/**`) + and do NOT change compiler/linker flags (e.g. `-stdlib=libc++`) to work around a build error. You also CANNOT + install packages (no apt/sudo inside the sandbox, and the firewall blocks OS package mirrors). If a native + build genuinely fails because a dependency is missing from the host, that is a workflow bug: STOP, do not hack + the build, and record it in the mono/SkiaSharp summary under "items needing human attention" so the + `Install native build dependencies` step can be fixed. - **NEVER run `externals-download`** in this workflow β€” not even for debugging or baseline comparison. Build from source only. - **Phase 9 reminder**: a green C# build is NOT sufficient - run the new-function diff check from Phase 9 step 1. - **Phase 11 β€” do NOT execute it.** Replace it entirely with the file writes below. From 8cc6f5d9768e5a10ae3ff3c77f3c55846cfcc2a1 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 01:11:16 +0200 Subject: [PATCH 06/11] Resolve sync mode from cron in the workflow, pass script args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cronβ†’mode mapping (7 AM β†’ current, 5 PM β†’ latest, else β†’ next) was the only workflow-coupled knowledge inside skia-sync-detect.sh β€” it hardcoded the literal cron strings and read mode/milestone/schedule from INPUT_MODE, INPUT_MILESTONE and SCHEDULE env vars. Move that mapping into the workflow, next to where the crons are declared, as a single GitHub Actions expression. The detector now takes real --mode/--milestone arguments and no longer knows anything about cron, so it is self-contained and unit-testable (pass --mode latest instead of faking SCHEDULE='0 17 * * *'). mode is staged into an env var (not interpolated straight into run:) so the free-form milestone dispatch input cannot inject shell β€” the same anti-injection safety the previous env-based wiring had. Behavior is unchanged: validated that --mode current/next/latest, the default, an explicit --milestone override, an empty --milestone, and an unknown arg all resolve exactly as before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/skia-sync-detect.sh | 47 ++++++++++------- .github/workflows/auto-skia-sync.lock.yml | 63 +++++++++++++---------- .github/workflows/auto-skia-sync.md | 35 ++++++++----- 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/.github/scripts/skia-sync-detect.sh b/.github/scripts/skia-sync-detect.sh index ba2bf39b422..9aea5fa0565 100755 --- a/.github/scripts/skia-sync-detect.sh +++ b/.github/scripts/skia-sync-detect.sh @@ -14,22 +14,37 @@ # - All human-readable logs and ::error::/::notice:: workflow commands go to stdout # (so GitHub renders annotations and the machine output stays clean for sourcing). # +# Args: +# --mode Which milestone line to track. The workflow resolves +# this from the dispatch input or the triggering cron +# (the cronβ†’mode mapping lives in the workflow, next to +# where the crons are declared). Defaults to `next`. +# --milestone Exact milestone override; when set, mode becomes +# `explicit` and the target is this number. +# --gate Also run the gating checks (does upstream exist? is it +# already merged?) and emit `skip=true` / exit +# accordingly. Only pre_activation needs this. +# # Inputs (env): -# INPUT_MODE github.event.inputs.mode (current|next|latest, optional) -# INPUT_MILESTONE github.event.inputs.milestone (exact number, optional override) -# SCHEDULE github.event.schedule (cron string, optional) # GITHUB_REPOSITORY owner/repo of this SkiaSharp checkout (default GitHub env) # GITHUB_REF ref whose scripts/VERSIONS.txt gives main's milestone # GH_TOKEN token for `gh api` -# -# Flags: -# --gate Also run the gating checks (does upstream exist? is it already merged?) -# and emit `skip=true` / exit accordingly. Only pre_activation needs this. set -euo pipefail GATE=false -[ "${1:-}" = "--gate" ] && GATE=true +MODE="" +MILESTONE="" +while [ $# -gt 0 ]; do + case "$1" in + --gate) GATE=true ;; + --mode) MODE="${2:-}"; shift ;; + --milestone) MILESTONE="${2:-}"; shift ;; + *) echo "::error::skia-sync-detect.sh: unknown argument '$1'"; exit 2 ;; + esac + shift +done +MODE="${MODE:-next}" OUT="${SKIA_SYNC_OUT:-${GITHUB_OUTPUT:?SKIA_SYNC_OUT or GITHUB_OUTPUT must be set}}" emit() { printf '%s=%s\n' "$1" "$2" >>"$OUT"; } @@ -41,24 +56,16 @@ milestone_of() { } # -- Mode ------------------------------------------------------------- -if [ -n "${INPUT_MODE:-}" ]; then - MODE="$INPUT_MODE" -elif [ "${SCHEDULE:-}" = "0 7 * * *" ]; then - MODE=current -elif [ "${SCHEDULE:-}" = "0 17 * * *" ]; then - MODE=latest -else - MODE=next -fi - +# Resolved by the caller (workflow): the cronβ†’mode mapping lives in the workflow, +# next to where the crons are declared. Here it is simply a value in {current,next,latest}. MAIN_MS=$(milestone_of "$GITHUB_REF") NEXT=$((MAIN_MS + 1)) LATEST=$(git ls-remote --heads https://github.com/google/skia.git 'refs/heads/chrome/m*' \ | sed -n 's|.*refs/heads/chrome/m\([0-9]*\)$|\1|p' | sort -n | tail -1) # -- Target ----------------------------------------------------------- -if [ -n "${INPUT_MILESTONE:-}" ]; then - TARGET="$INPUT_MILESTONE"; MODE="explicit" +if [ -n "$MILESTONE" ]; then + TARGET="$MILESTONE"; MODE="explicit" elif [ "$MODE" = latest ]; then TARGET="$LATEST" elif [ "$MODE" = current ]; then diff --git a/.github/workflows/auto-skia-sync.lock.yml b/.github/workflows/auto-skia-sync.lock.yml index 1f10eccd233..688dbab6b71 100644 --- a/.github/workflows/auto-skia-sync.lock.yml +++ b/.github/workflows/auto-skia-sync.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f837ac9c789c59020f6a17a866aa3a43e2086a6421e464b3bf1f406c03a15937","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"cd20e5a36642ecbadd9af4a79e85a6349ca1d4e6273785f00f9fca538424015a","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -65,12 +65,15 @@ name: "Skia Upstream Sync" # sparse-checkout: .github/scripts # - env: # GH_TOKEN: ${{ github.token }} - # INPUT_MILESTONE: ${{ github.event.inputs.milestone }} - # INPUT_MODE: ${{ github.event.inputs.mode }} - # SCHEDULE: ${{ github.event.schedule }} + # MILESTONE: ${{ github.event.inputs.milestone }} + # MODE: |- + # ${{ github.event.inputs.mode + # || (github.event.schedule == '0 7 * * *' && 'current') + # || (github.event.schedule == '0 17 * * *' && 'latest') + # || 'next' }} # id: detect # name: Detect milestone - # run: bash .github/scripts/skia-sync-detect.sh --gate + # run: bash .github/scripts/skia-sync-detect.sh --gate --mode "$MODE" --milestone "$MILESTONE" workflow_dispatch: inputs: aw_context: @@ -231,23 +234,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' + cat << 'GH_AW_PROMPT_8ff49e7cd67c6b58_EOF' - GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF + GH_AW_PROMPT_8ff49e7cd67c6b58_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' + cat << 'GH_AW_PROMPT_8ff49e7cd67c6b58_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF + GH_AW_PROMPT_8ff49e7cd67c6b58_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' + cat << 'GH_AW_PROMPT_8ff49e7cd67c6b58_EOF' - GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF + GH_AW_PROMPT_8ff49e7cd67c6b58_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' + cat << 'GH_AW_PROMPT_8ff49e7cd67c6b58_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -279,12 +282,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF + GH_AW_PROMPT_8ff49e7cd67c6b58_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF' + cat << 'GH_AW_PROMPT_8ff49e7cd67c6b58_EOF' {{#runtime-import .github/workflows/auto-skia-sync.md}} - GH_AW_PROMPT_d9ac9aa4ffbf489f_EOF + GH_AW_PROMPT_8ff49e7cd67c6b58_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -455,7 +458,7 @@ jobs: - name: Align submodule to the base branch run: | OUT=$(mktemp) - SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh + SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh --mode "$MODE" --milestone "$MILESTONE" set -a; . "$OUT"; set +a bash .github/scripts/skia-sync-align-submodule.sh env: @@ -464,10 +467,13 @@ jobs: GH_TOKEN: ${{ github.token }} GITHUB_API_URL: https://localhost:18443/api/v3 GITHUB_GRAPHQL_URL: https://localhost:18443/api/graphql - INPUT_MILESTONE: ${{ github.event.inputs.milestone }} - INPUT_MODE: ${{ github.event.inputs.mode }} + MILESTONE: ${{ github.event.inputs.milestone }} + MODE: |- + ${{ github.event.inputs.mode + || (github.event.schedule == '0 7 * * *' && 'current') + || (github.event.schedule == '0 17 * * *' && 'latest') + || 'next' }} NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt - SCHEDULE: ${{ github.event.schedule }} - name: Copy push script for post-step run: cp .github/scripts/skia-sync-push-prs.sh /tmp/gh-aw/skia-sync-push-prs.sh env: @@ -547,9 +553,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_23016918b95809af_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_282d84f8ec931f90_EOF' {"create_issue":{"labels":["auto-skia-sync"],"max":1,"title_prefix":"[auto-skia-sync]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_23016918b95809af_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_282d84f8ec931f90_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -747,7 +753,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_81e9d30c8bfb6908_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_e17cf7bba83b9a58_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -795,7 +801,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_81e9d30c8bfb6908_EOF + GH_AW_MCP_CONFIG_e17cf7bba83b9a58_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1397,12 +1403,15 @@ jobs: sparse-checkout: .github/scripts - name: Detect milestone id: detect - run: bash .github/scripts/skia-sync-detect.sh --gate + run: bash .github/scripts/skia-sync-detect.sh --gate --mode "$MODE" --milestone "$MILESTONE" env: GH_TOKEN: ${{ github.token }} - INPUT_MILESTONE: ${{ github.event.inputs.milestone }} - INPUT_MODE: ${{ github.event.inputs.mode }} - SCHEDULE: ${{ github.event.schedule }} + MILESTONE: ${{ github.event.inputs.milestone }} + MODE: |- + ${{ github.event.inputs.mode + || (github.event.schedule == '0 7 * * *' && 'current') + || (github.event.schedule == '0 17 * * *' && 'latest') + || 'next' }} safe_outputs: needs: diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 855470b9062..a77e3d32cf7 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -45,12 +45,20 @@ on: sparse-checkout: .github/scripts - name: Detect milestone id: detect + # The cronβ†’mode mapping lives HERE, next to the cron declarations: + # 7 AM β†’ current, 5 PM β†’ latest, everything else (12 PM cron + the + # workflow_dispatch default) β†’ next. Staged into env vars rather than + # interpolated straight into `run:`, so the free-form `milestone` input + # can't inject shell β€” the script consumes them as real --mode/--milestone args. env: - INPUT_MODE: ${{ github.event.inputs.mode }} - INPUT_MILESTONE: ${{ github.event.inputs.milestone }} - SCHEDULE: ${{ github.event.schedule }} + MODE: >- + ${{ github.event.inputs.mode + || (github.event.schedule == '0 7 * * *' && 'current') + || (github.event.schedule == '0 17 * * *' && 'latest') + || 'next' }} + MILESTONE: ${{ github.event.inputs.milestone }} GH_TOKEN: ${{ github.token }} - run: bash .github/scripts/skia-sync-detect.sh --gate + run: bash .github/scripts/skia-sync-detect.sh --gate --mode "$MODE" --milestone "$MILESTONE" # -- Pre-activation outputs ------------------------------------------ # Expose detect step outputs for use in the prompt and other jobs. @@ -140,18 +148,21 @@ steps: run: | mkdir -p /tmp/gh-aw/agent - name: Align submodule to the base branch + # Same cronβ†’mode resolution as the pre_activation detect step (see there). The + # agent job can't read pre_activation's outputs (it only `needs:` activation), so + # re-run the same committed detector to recover base_branch / skia_base_branch, + # then align the submodule. skia-sync-detect.sh is the single source of truth. env: - INPUT_MODE: ${{ github.event.inputs.mode }} - INPUT_MILESTONE: ${{ github.event.inputs.milestone }} - SCHEDULE: ${{ github.event.schedule }} + MODE: >- + ${{ github.event.inputs.mode + || (github.event.schedule == '0 7 * * *' && 'current') + || (github.event.schedule == '0 17 * * *' && 'latest') + || 'next' }} + MILESTONE: ${{ github.event.inputs.milestone }} GH_TOKEN: ${{ github.token }} - # The agent job can't read pre_activation's outputs (it only `needs:` - # activation), so re-run the same committed detector to recover base_branch / - # skia_base_branch, then align the submodule. skia-sync-detect.sh is the single - # source of truth β€” no branch logic is duplicated here. run: | OUT=$(mktemp) - SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh + SKIA_SYNC_OUT="$OUT" bash .github/scripts/skia-sync-detect.sh --mode "$MODE" --milestone "$MILESTONE" set -a; . "$OUT"; set +a bash .github/scripts/skia-sync-align-submodule.sh - name: Copy push script for post-step From fe685571bbcee85cb3ffece4c0508dad08ff72f1 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 01:24:15 +0200 Subject: [PATCH 07/11] Tidy sync scripts after shellcheck review Final review pass over the skia-sync scripts: - Harden skia-sync-detect.sh arg parsing: a value-less --mode/--milestone now reports a clear error and exits 2 instead of tripping set -e on a double shift. Matters now that the detector is meant to be run by hand for testing. - Flatten the multi-match release-branch list with paste instead of the $(echo $VAR) word-splitting idiom (SC2116/SC2086). - Mark the runtime-generated skia-sync-env.sh source with a shellcheck source=/dev/null directive (SC1091). All three scripts are now shellcheck-clean at style level; behavior verified unchanged for every valid invocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/skia-sync-detect.sh | 14 +++++++++----- .github/scripts/skia-sync-push-prs.sh | 2 ++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/scripts/skia-sync-detect.sh b/.github/scripts/skia-sync-detect.sh index 9aea5fa0565..d5a6d6858b5 100755 --- a/.github/scripts/skia-sync-detect.sh +++ b/.github/scripts/skia-sync-detect.sh @@ -37,12 +37,15 @@ MODE="" MILESTONE="" while [ $# -gt 0 ]; do case "$1" in - --gate) GATE=true ;; - --mode) MODE="${2:-}"; shift ;; - --milestone) MILESTONE="${2:-}"; shift ;; + --gate) GATE=true; shift ;; + --mode) + [ $# -ge 2 ] || { echo "::error::skia-sync-detect.sh: --mode requires a value"; exit 2; } + MODE="$2"; shift 2 ;; + --milestone) + [ $# -ge 2 ] || { echo "::error::skia-sync-detect.sh: --milestone requires a value"; exit 2; } + MILESTONE="$2"; shift 2 ;; *) echo "::error::skia-sync-detect.sh: unknown argument '$1'"; exit 2 ;; esac - shift done MODE="${MODE:-next}" @@ -93,7 +96,8 @@ if [ "$TARGET" -lt "$MAIN_MS" ] 2>/dev/null; then # The glob can match more than one major (e.g. release/4.148.x and a stray # release/14.148.x). Refuse to guess β€” fail so a human disambiguates. if [ "$(printf '%s' "$RELEASE_BRANCH" | grep -c . || true)" -gt 1 ]; then - echo "::error::Multiple release branches match milestone ${TARGET}: $(echo $RELEASE_BRANCH) β€” cannot disambiguate." + matches=$(echo "$RELEASE_BRANCH" | paste -sd' ' -) + echo "::error::Multiple release branches match milestone ${TARGET}: ${matches} β€” cannot disambiguate." exit 1 fi fi diff --git a/.github/scripts/skia-sync-push-prs.sh b/.github/scripts/skia-sync-push-prs.sh index d004554650a..393ae96a1ce 100755 --- a/.github/scripts/skia-sync-push-prs.sh +++ b/.github/scripts/skia-sync-push-prs.sh @@ -18,6 +18,8 @@ if [ ! -f /tmp/gh-aw/agent/skia-sync-env.sh ]; then echo "No skia-sync-env.sh β€” agent determined no work needed" exit 0 fi +# Written by the agent at runtime; not resolvable at lint time. +# shellcheck source=/dev/null source /tmp/gh-aw/agent/skia-sync-env.sh if [ -z "${TARGET:-}" ]; then From ad07d6638d24658bc62efc44b2c479096d3d8847 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 21:00:14 +0200 Subject: [PATCH 08/11] Harden Skia-sync conflict policy and allow durable gn args in build.cake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first `mode=main` (tip) run (PR #4254 validation) surfaced two rough edges in the upstream-sync automation worth fixing before tip-mode becomes routine. ~~ Verify-upstream-or-reapply conflict resolution ~~ A tip merge is ~291 commits ahead of our submodule base, so it conflicts on every file that carries a `[M1xx]` fork cherry-pick. The old guidance was a blunt "keep ours" / "resolve what you reasonably can", which risks two failure modes: silently dropping a not-yet-upstreamed fork patch with `--theirs`, or freezing a stale form of a patch upstream has since refined with `--ours` (e.g. our SDF-LCD patch used `getMaxScale()`; upstream relanded it as `sk_ieee_float_divide(1.f, getMinScale())`). Replace it with an explicit, auditable policy: for each conflicted file, classify every fork patch as *upstreamed* (take upstream's refined form) or *not upstreamed* (re-apply our patch on top of upstream) β€” never a blanket `--theirs`/`--ours`, never a silent drop. A mandatory before-merge snapshot of fork patches must be cross-referenced so every fork patch on a conflicted file lands in the PR's "Conflicts resolved" table as upstreamed or re-applied. A patch that is neither is a lost patch β€” stop and fix it. Applied in gotcha #15, SKILL.md Phase 5, and the workflow's tip-mode note. ~~ Allow a required new gn arg through build.cake ~~ The tip build needed `skia_use_partition_alloc=false` because upstream now defaults `skia_use_partition_alloc=is_clang` while our DEPS deliberately disables `third_party/externals/partition_alloc` (Skia ships an official zero-overhead noop `raw_ptr` for exactly this case). The agent passed it as a one-off `--gnArgs` CLI flag because the rule banned all `build.cake` edits β€” durable config ending up non-durable. Relax the rule into two tiers: host/flag hacks to silence a build error stay FORBIDDEN (they break the platforms the Linux-only sync never builds), but a genuinely *required* new upstream gn arg may be added to the affected platforms' `native/**/build.cake` gn-args lists (the single source of truth, next to the existing `skia_use_*` toggles) and flagged in both PR summaries for cross-platform human review. This lets the next tip merge bake the toggle in durably β€” and correctly ships it alongside the milestone-bearing submodule, so it never breaks the current m150 build (which has no such gn arg yet). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .agents/skills/update-skia/SKILL.md | 26 ++++++++-- .../update-skia/references/known-gotchas.md | 49 ++++++++++++++----- .github/workflows/auto-skia-sync.md | 35 +++++++++---- 3 files changed, 86 insertions(+), 24 deletions(-) diff --git a/.agents/skills/update-skia/SKILL.md b/.agents/skills/update-skia/SKILL.md index 219326fbba5..ec16642bd97 100644 --- a/.agents/skills/update-skia/SKILL.md +++ b/.agents/skills/update-skia/SKILL.md @@ -305,9 +305,18 @@ You should still be inside `externals/skia` from Phase 4. 2. **Resolve conflicts** β€” each conflict must be resolved individually. Never use `git merge -s ours` or `git read-tree --reset` β€” this destroys `git blame` attribution. - **⚠️ MANDATORY: Before resolving ANY conflict, check file history for fork-specific patches.** - Run `git log --oneline skiasharp -- ` β€” if the log shows intentional - fork patches, keep our version. See [gotcha #15](references/known-gotchas.md) for details. + **⚠️ MANDATORY: classify every fork patch as *upstreamed* or *not* before resolving.** + For each conflicted file, list the fork patches touching it and check whether upstream already + carries each one (see [gotcha #15](references/known-gotchas.md) for the exact commands): + + ```bash + git log --oneline {SKIA_BASE_BRANCH} -- # our fork patches on this file + git log -S "" --oneline upstream/{UPSTREAM_REF} # did upstream adopt it? + ``` + + - **Upstreamed** β†’ take upstream's (possibly refined) form; record `"" upstreamed as `. + - **Not upstreamed** β†’ re-apply our patch on top of upstream's edits; **never drop it**; record `re-applied`. + - **Never** blanket `git checkout --theirs`/`--ours` on a file you have not classified. | File Category | Strategy | |--------------|----------| @@ -315,7 +324,16 @@ You should still be inside `externals/skia` from Phase 4. | `DEPS` | **Combine** β€” keep our dependency pins, accept upstream structure | | `RELEASE_NOTES.md`, `infra/` | **Take upstream** | | C API (`include/c/`, `src/c/`) | **Keep SkiaSharp** β€” adapt includes/API calls in post-merge commits | - | Other upstream source (`src/`, `include/`) | **Check history first** β€” see [gotcha #15](references/known-gotchas.md) | + | Other upstream source (`src/`, `include/`) | **Verify-upstream-or-reapply** β€” see [gotcha #15](references/known-gotchas.md) | + + **Audit (mandatory).** Snapshot fork patches before merging, then cross-reference every conflict: + ```bash + MB=$(git merge-base {SKIA_BASE_BRANCH} upstream/{UPSTREAM_REF}) + git log --oneline "$MB..{SKIA_BASE_BRANCH}" > /tmp/fork-patches-before.txt + ``` + For every conflicted file, the fork patch(es) touching it must appear in the mono/skia PR's "Conflicts + resolved" table as *upstreamed* or *re-applied*. A fork patch on a conflicted file that is neither is a + lost patch β€” STOP and fix it. (Patches whose files did not conflict merge cleanly and need no listing.) 3. **Commit the merge**: ```bash diff --git a/.agents/skills/update-skia/references/known-gotchas.md b/.agents/skills/update-skia/references/known-gotchas.md index fa38220aab9..09833ef1d0f 100644 --- a/.agents/skills/update-skia/references/known-gotchas.md +++ b/.agents/skills/update-skia/references/known-gotchas.md @@ -118,25 +118,52 @@ Never use a tree-override merge (`git merge -s ours`, `git read-tree --reset`). | C API source (`src/c/`) | **Keep SkiaSharp + adapt** β€” fix includes and API calls in post-merge commits | | Other upstream source (`src/`, `include/`) | **Check history first** β€” see gotcha #15 | -### 15. Never `--theirs` Without Checking File History +### 15. Verify-Upstream-or-Reapply β€” Never Blanket `--theirs`/`--ours` -**Failure mode**: A merge conflict in an upstream file (outside `src/c/` / `include/c/`) is resolved -with `git checkout --theirs`, silently overwriting an intentional SkiaSharp fork patch. +**Failure mode (two directions):** +- A conflict in an upstream file (outside `src/c/` / `include/c/`) is resolved with + `git checkout --theirs`, silently **dropping** an intentional SkiaSharp fork patch. +- Or our side is blindly kept with `--ours`, **freezing a stale form** of a patch that upstream + has since adopted and *refined* (e.g. our `[M150] Turn off LCD in SDF slugs` used `getMaxScale()`; + upstream relanded it as `sk_ieee_float_divide(1.f, getMinScale())` β€” keeping ours would have + shipped the worse formulation). -**Mandatory process for EVERY conflicted file:** +A fork patch in a conflicted file is in exactly **one of two states**. You MUST determine which +*before* resolving, and resolve accordingly. A blanket `--theirs`/`--ours` is never acceptable. ```bash -# BEFORE resolving, check if the fork has intentional patches -git log --oneline skiasharp -- +# 1. Identify the fork patch(es) touching this file +git log --oneline {SKIA_BASE_BRANCH} -- # e.g. skiasharp +# 2. For EACH fork patch, check whether upstream already contains the same change +git log --oneline upstream/{UPSTREAM_REF} --grep "" +git log -S "" --oneline upstream/{UPSTREAM_REF} -- ``` -- If the log shows fork-specific commits (look for "Restore", "patch", "fix", or any non-merge - commit), **keep our version** and only absorb upstream's harmless additive changes (new includes). -- If the log shows only merge commits from prior upstream merges, taking `--theirs` is likely safe. -- **Never use `git checkout --theirs` as a shortcut** for files you haven't investigated. +| Patch state | Resolution | +|---|---| +| **Upstreamed** β€” upstream now contains an equivalent (or refined) form | **Take upstream's form.** Our patch is redundant; convergence is correct (upstream may have improved it). Record `"" upstreamed as `. | +| **Not upstreamed** β€” only our fork carries it | **Re-apply our change on top of upstream's edits** (absorb upstream's harmless additions AND keep our patch). **Never drop it.** Record `"" re-applied`. | + +- **Never** `git checkout --theirs`/`--ours` as a shortcut for a file you have not classified. +- **Never** "resolve what you reasonably can and move on" β€” that is how a fork patch gets lost. + +**Mandatory audit β€” no fork patch silently dropped.** Snapshot the fork patches *before* merging so +you can cross-reference every conflict against them: + +```bash +# before the merge β€” list our fork's commits on top of the merge base (the patches at risk) +MB=$(git merge-base {SKIA_BASE_BRANCH} upstream/{UPSTREAM_REF}) +git log --oneline "$MB..{SKIA_BASE_BRANCH}" > /tmp/fork-patches-before.txt +``` + +For **every conflicted file**, find which fork patch(es) from that list touch it and classify each as +*upstreamed* or *re-applied* (above). Every such patch must appear in the mono/skia PR's "Conflicts +resolved" table with its disposition. A fork patch on a conflicted file that is **neither** upstreamed +nor re-applied is a lost patch β€” STOP and fix it before committing the merge. (Fork patches whose files +did not conflict merge cleanly and need no listing.) **Key signal words** in commit messages that indicate intentional fork patches: -`Restore`, `patch`, `fix for`, `platform`, `workaround`, `SkiaSharp`, `iOS`, `Tizen` +`Restore`, `patch`, `fix for`, `platform`, `workaround`, `SkiaSharp`, `iOS`, `Tizen`, `[M1xx]` ## Testing diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index c957fb02338..1b163273b50 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -226,9 +226,15 @@ Release-line sync: `${{ needs.pre_activation.outputs.is_release }}`. > **bleeding-edge sync from the very tip of upstream Skia** (google/skia `main` HEAD), not a `chrome/m` > milestone branch. It targets the newest line (`main`/`skiasharp`) and `current == target`, so it is > **NOT a version bump** β€” keep the milestone, soname, and nuget versions unchanged. It may still include -> new APIs / binding changes (regenerate + build + test as normal), and a tip merge can be large and -> conflict-heavy because the submodule base is well behind google/skia main; resolve what you reasonably -> can and record anything unresolved under "items needing human attention". +> new APIs / binding changes (regenerate + build + test as normal). A tip merge is large and conflict-heavy +> because the submodule base is well behind google/skia main, so the **verify-upstream-or-reapply** policy +> (Phase 5 / [gotcha #15](.agents/skills/update-skia/references/known-gotchas.md)) is mandatory: for every +> conflicted file, classify each fork patch as *upstreamed* (take upstream's refined form) or *not +> upstreamed* (re-apply our patch on top of upstream) β€” **never blanket `--theirs`/`--ours`, and never +> silently drop a fork patch**. Snapshot the fork patches before merging +> (`git log --oneline $(git merge-base skiasharp upstream/main)..skiasharp`) and account for **every one** +> in the mono/skia PR's "Conflicts resolved" table as upstreamed (``) or re-applied. A patch that is +> neither is a lost patch β€” STOP rather than ship it. **Read `.agents/skills/update-skia/SKILL.md` and follow Phases 2-10.** Notes specific to this automated workflow: @@ -254,12 +260,23 @@ Release-line sync: `${{ needs.pre_activation.outputs.is_release }}`. - **Build platform**: use Linux x64 (`dotnet cake --target=externals-linux --arch=x64`). Clang is pre-configured via env vars. This also applies to Phase 10 if a native rebuild is needed. - **Native build environment is fully provisioned by the workflow** (clang, `libc++-dev`/`libc++abi-dev`, - fontconfig, ninja). Do NOT modify any native build files (`native/**/build.cake`, `scripts/infra/native/**`) - and do NOT change compiler/linker flags (e.g. `-stdlib=libc++`) to work around a build error. You also CANNOT - install packages (no apt/sudo inside the sandbox, and the firewall blocks OS package mirrors). If a native - build genuinely fails because a dependency is missing from the host, that is a workflow bug: STOP, do not hack - the build, and record it in the mono/SkiaSharp summary under "items needing human attention" so the - `Install native build dependencies` step can be fixed. + fontconfig, ninja). You CANNOT install packages (no apt/sudo inside the sandbox, and the firewall blocks + OS package mirrors). Two distinct cases β€” do not conflate them: + - **Host/flag hacks to silence an error β€” FORBIDDEN.** Do NOT change compiler/linker flags (e.g. + `-stdlib=libc++`) or touch `scripts/infra/native/**` to work around a build error on this Linux host. + Those files are shared across every platform you do NOT build here (Windows/macOS/iOS/Android/WASM), so a + host-specific tweak silently breaks real CI. If a build fails because a host dependency is missing, that + is a workflow bug: STOP, do not hack the build, and record it under "items needing human attention" so the + `Install native build dependencies` step can be fixed. + - **A new *required* upstream gn arg β€” ALLOWED, via `build.cake`.** If the upstream merge introduces a new + gn argument that the build genuinely cannot succeed without (e.g. a new dependency our fork does not vendor, + such as `skia_use_partition_alloc=false` when `third_party/externals/partition_alloc` is disabled in our + `DEPS`), add that gn arg to the affected platforms' `native/**/build.cake` gn-args lists β€” that file is the + single source of truth and belongs next to the existing `skia_use_*` toggles. Do NOT rely on a one-off + `--gnArgs` CLI flag for durable config. Because you only build Linux x64 here, apply the same arg to every + clang platform's `build.cake` that needs it and call the change out prominently in BOTH PR summaries under + "items needing human attention" for cross-platform human review. This is only for a genuinely required + arg β€” never to silence a host-specific error (see the FORBIDDEN case above). - **NEVER run `externals-download`** in this workflow β€” not even for debugging or baseline comparison. Build from source only. - **Phase 9 reminder**: a green C# build is NOT sufficient - run the new-function diff check from Phase 9 step 1. - **Phase 11 β€” do NOT execute it.** Replace it entirely with the file writes below. From 35ee0689b486a6c41af9d9ac09a5936e9b8bcf1c Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 21:54:30 +0200 Subject: [PATCH 09/11] [skia-sync] Move tip-mode + gn-arg knowledge into the skill; thin the workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auto-skia-sync workflow prompt had accumulated general Skia-update knowledge that belongs in the update-skia skill, not in CI-specific prose. Two concepts in particular were documented ONLY in the workflow, so the skill couldn't drive a local/manual run of them: 1. `main`/tip mode β€” syncing from the very tip of google/skia `main` (HEAD) instead of a `chrome/m{N}` milestone branch. Bleeding-edge, `CURRENT == TARGET`, NOT a version bump, but may carry new APIs. 2. The "a required new upstream gn arg goes in native/**/build.cake, not a one-off --gnArgs flag" rule (e.g. skia_use_partition_alloc =false when our DEPS doesn't vendor partition_alloc). Move both into the skill so it works standalone, and reduce the workflow to minimal CI-specific overrides + pointers (option B: duplicated reminders kept as short safety one-liners, since the unattended CI agent benefits from repeated guardrails). Skill (works locally now): * SKILL.md Phase 1: tip mode is a first-class third option alongside main-target and release-line-target β€” variable table gains a {UPSTREAM_REF} row and a tip column, plus a paragraph covering the not-a-version-bump semantics and how Phase 2/5 milestone diffs become merge-base..upstream/main for the tip. * SKILL.md Phase 5: merge command generalized to upstream/{UPSTREAM_REF}. * SKILL.md Phase 7: new error-table row + note for a required new gn arg, cross-referencing the gotcha. * known-gotchas.md: new gotcha #23 β€” partition_alloc worked example (why our DEPS un-vendors it, why the noop raw_ptr / =false is the supported embedder path, not vendoring) and the milestone-sequencing caveat (the arg doesn't exist before its upstream branch point, so only add it alongside the submodule that carries it). Workflow (body-only edits; lock stays byte-identical, frontmatter unchanged): * Mode/tip note and the FORBIDDEN/ALLOWED build.cake block collapsed to short pointers into the skill, keeping the genuinely CI-specific parts: can't apt-install in the sandbox, a missing host dependency is a workflow bug (fix the install step, don't hack shared flags), and the Linux-only-build cross-platform review reminder. gh aw compile auto-skia-sync: 0 errors, 4 pre-existing warnings, lock unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .agents/skills/update-skia/SKILL.md | 43 ++++++++++--- .../update-skia/references/known-gotchas.md | 11 ++++ .github/workflows/auto-skia-sync.md | 61 +++++++------------ 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/.agents/skills/update-skia/SKILL.md b/.agents/skills/update-skia/SKILL.md index ec16642bd97..19038e1900a 100644 --- a/.agents/skills/update-skia/SKILL.md +++ b/.agents/skills/update-skia/SKILL.md @@ -126,10 +126,12 @@ E. Ship (Phase 11) ```bash cd externals/skia git remote add upstream https://github.com/google/skia.git 2>/dev/null - git fetch upstream chrome/m{TARGET} + git fetch upstream {UPSTREAM_REF} # chrome/m{TARGET} for a milestone, or main for tip mode ``` + `{UPSTREAM_REF}` is the upstream ref you merge from: `chrome/m{TARGET}` for a normal + milestone/release-line update, or `main` for a `main`/tip sync (see step 5). > **Note:** When this phase is pre-computed by the automated workflow, you still need to - > add the `upstream` remote and fetch `chrome/m{TARGET}` β€” Phase 5 depends on it. + > add the `upstream` remote and fetch `{UPSTREAM_REF}` β€” Phase 5 depends on it. 5. **Determine the base branch (`main` vs a release line).** Most updates target `main` (newest, in-development line) with `skiasharp` as the mono/skia base. But when the target @@ -143,11 +145,12 @@ E. Ship (Phase 11) git -C externals/skia ls-remote --heads origin "refs/heads/release/*.{TARGET}.x" ``` - | Variable | `main` target | release-line target | - |----------|---------------|---------------------| - | `{BASE_BRANCH}` (SkiaSharp) | `main` | `release/.{TARGET}.x` | - | `{SKIA_BASE_BRANCH}` (mono/skia) | `skiasharp` | `release/.{TARGET}.x` | - | `{HEAD_BRANCH}` (both repos) | `skia-sync/m{TARGET}` | `skia-sync/release-.{TARGET}.x` | + | Variable | `main` target | release-line target | `main`/tip mode | + |----------|---------------|---------------------|-----------------| + | `{UPSTREAM_REF}` (merge from) | `chrome/m{TARGET}` | `chrome/m{TARGET}` | `main` (HEAD) | + | `{BASE_BRANCH}` (SkiaSharp) | `main` | `release/.{TARGET}.x` | `main` | + | `{SKIA_BASE_BRANCH}` (mono/skia) | `skiasharp` | `release/.{TARGET}.x` | `skiasharp` | + | `{HEAD_BRANCH}` (both repos) | `skia-sync/m{TARGET}` | `skia-sync/release-.{TARGET}.x` | `skia-sync/main` | A release-line update is always a **bug-fix-only sync** (`CURRENT == TARGET`): do NOT bump the milestone/soname/nuget version in Phase 6 β€” only `cgmanifest.json`'s commit hash changes. @@ -157,6 +160,20 @@ E. Ship (Phase 11) > `release/.{TARGET}.x` branch exists but the mono/skia one does NOT, **STOP and fail** β€” > do not create it here. Ask a human to cut the mono/skia release branch first. + **`main` (tip) mode** is a third option, distinct from both targets above: instead of a + `chrome/m{TARGET}` milestone branch, merge the very tip of upstream `google/skia` `main` + (HEAD). It targets the newest line (`{BASE_BRANCH}` = `main`, `{SKIA_BASE_BRANCH}` = + `skiasharp`, `{HEAD_BRANCH}` = `skia-sync/main`, `{UPSTREAM_REF}` = `main`) and `CURRENT == + TARGET`, so it is **NOT a version bump** β€” keep the milestone/soname/nuget versions + unchanged. Unlike a release-line bug-fix sync it MAY still include new upstream API/binding + changes, so regenerate + build + test as normal (Phases 8–10). A tip merge is large and + conflict-heavy because the submodule base is well behind `main`, so the + verify-upstream-or-reapply policy (Phase 5 / [gotcha #15](references/known-gotchas.md)) is + mandatory. Because the tip is not a milestone branch, the milestone-to-milestone diffs in + Phase 2 and Phase 5 don't apply directly β€” substitute + `$(git merge-base {SKIA_BASE_BRANCH} upstream/main)..upstream/main` wherever those phases + diff `chrome/m{CURRENT}..chrome/m{TARGET}`. + Use these `{BASE_BRANCH}` / `{SKIA_BASE_BRANCH}` / `{HEAD_BRANCH}` values everywhere below in place of the hardcoded `main` / `skiasharp` / `skia-sync/m{TARGET}` defaults. @@ -299,7 +316,7 @@ You should still be inside `externals/skia` from Phase 4. 1. **Merge upstream** β€” use `--no-commit` for manual conflict resolution: ```bash - git merge --no-commit upstream/chrome/m{TARGET} + git merge --no-commit upstream/{UPSTREAM_REF} # chrome/m{TARGET}, or main in tip mode ``` 2. **Resolve conflicts** β€” each conflict must be resolved individually. @@ -425,6 +442,16 @@ must be updated when the underlying C++ APIs change. | Changed signature | Update C wrapper function signature | | New header required | Add `#include` in the relevant `.cpp` | | Legacy flag breaks C API | Update C API to use replacement API (see gotcha #6). Do not just comment out the flag without a plan | + | New *required* upstream gn arg | A new upstream dependency our fork doesn't vendor may need a gn toggle (e.g. `skia_use_partition_alloc=false`). Add it to the affected platforms' `native/**/build.cake` gn-args lists β€” NOT a one-off `--gnArgs` flag (see [gotcha #23](references/known-gotchas.md)) | + + > **GN args belong in `build.cake`, not CLI flags.** When the upstream merge introduces a + > *genuinely required* new gn argument (typically a dependency our `DEPS` deliberately doesn't + > vendor), add it to **every affected platform's** `native/**/build.cake` gn-args list β€” that + > file is the single source of truth, next to the existing `skia_use_*` toggles. Don't paper + > over it with a one-off `dotnet cake … --gnArgs` flag (non-durable), and don't add gn args (or + > change compiler/linker flags) merely to silence a host-specific build error β€” that's a + > missing-dependency problem, not a config one. Full rationale + the `skia_use_partition_alloc` + > example and the milestone-sequencing caveat: [gotcha #23](references/known-gotchas.md). 3. **Update `sk_types.h`** for any new enums or type changes. Phase 6 reset `SK_C_INCREMENT` to 0. Only bump it if you add new C API functions in this milestone. diff --git a/.agents/skills/update-skia/references/known-gotchas.md b/.agents/skills/update-skia/references/known-gotchas.md index 09833ef1d0f..b4cce07eedc 100644 --- a/.agents/skills/update-skia/references/known-gotchas.md +++ b/.agents/skills/update-skia/references/known-gotchas.md @@ -61,6 +61,17 @@ The C API shims (`src/c/gr_context.cpp` etc.) compile as part of `:core`, but ba Upstream may move previously-core modules into separate optional targets. If the C API exposes functions from that module, add it as an explicit dependency of the `SkiaSharp` target in `BUILD.gn` rather than merging sources into core. +### 23. Required New Upstream GN Arg β†’ `build.cake` (not a one-off flag) + +Upstream sometimes introduces a build dependency our fork deliberately does **not** vendor. The fix is a durable gn arg in `build.cake` β€” not a CLI flag, and not a hack to silence the error. + +**Worked example β€” `skia_use_partition_alloc`.** Upstream's `gn/skia.gni` defaults `skia_use_partition_alloc = is_clang` (true on clang). When true, `BUILD.gn` imports `third_party/externals/partition_alloc/partition_alloc.gni` β€” but our fork's `DEPS` keeps that entry commented out, so the folder is never fetched and the build aborts. Skia ships an official zero-overhead **noop `raw_ptr`** for exactly this case ("because the partition_alloc dependency is missing"), gated on `SK_USE_PARTITION_ALLOC`, so building with `skia_use_partition_alloc=false` is the *supported embedder path*, not a workaround β€” it disables Chromium's MiraclePtr/BackupRefPtr hardening (irrelevant to SkiaSharp's trusted-app threat model) with no runtime regression. Vendoring the allocator instead would add a large Chromium dependency across the whole platform matrix for marginal benefit. + +**Resolution:** +- Add the gn arg to **every affected clang platform's** `native/**/build.cake` gn-args list, next to the existing `skia_use_*` toggles β€” that file is the single source of truth. Do **not** rely on a one-off `dotnet cake … --gnArgs` flag (non-durable, and easily lost). +- **Sequencing:** a gn arg added upstream *after* a milestone's branch point does not exist in that milestone. Adding it to `build.cake` before the submodule actually carries it fails the build with `Unknown build argument`. Only add it in the same change that advances the submodule to a tree that has the arg (e.g. a tip/`main` sync, or the milestone bump that introduces it). +- This is **only** for a genuinely required arg. Never add a gn arg β€” or change compiler/linker flags β€” merely to silence a build error on one host; that is a missing-dependency problem (the host toolchain/packages), not a build-config one. + ## Dependencies & Bindings ### 8. DEPS: Fork-Customized Dependencies diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 1b163273b50..2d3f5c38e8d 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -213,28 +213,18 @@ Base branch (SkiaSharp): `${{ needs.pre_activation.outputs.base_branch }}` β€” m Sync (head) branch: `${{ needs.pre_activation.outputs.head_branch }}` (same name in both repos). Release-line sync: `${{ needs.pre_activation.outputs.is_release }}`. -> **About the base branch.** Most syncs target `main` (the newest, in-development line) with -> `skiasharp` as the mono/skia base. When an older milestone is requested AND a matching -> `release/..x` branch exists, this is a **release-line sync**: both PRs target -> that release branch instead (mono/skia base = the same `release/..x` branch, -> which the release process guarantees exists). In that case `current == target`, so it is always a -> **bug-fix-only sync** β€” do NOT bump the milestone, soname, or nuget versions; only the upstream -> merge + `cgmanifest.json` commit hash change. Use the base/head branch values above everywhere -> instead of hardcoding `main`/`skiasharp`/`skia-sync/m{target}`. +> **Mode (resolved above β€” don't re-derive).** `is_release == true` β‡’ a **release-line bug-fix +> sync** (`current == target`): do NOT bump milestone/soname/nuget versions; only `cgmanifest.json`'s +> hash changes. `upstream_ref == main` β‡’ **`main`/tip mode** (head `skia-sync/main`): also not a +> version bump, but it MAY carry new APIs (regenerate + build + test as normal). Everything else is a +> normal milestone bump. See **[skill Phase 1](.agents/skills/update-skia/SKILL.md)** for what each +> mode means; always use the base/head values above, never hardcode `main`/`skiasharp`/`skia-sync/m{target}`. > -> **`main` (tip) mode.** When the upstream ref above is `main` (head branch `skia-sync/main`), this is a -> **bleeding-edge sync from the very tip of upstream Skia** (google/skia `main` HEAD), not a `chrome/m` -> milestone branch. It targets the newest line (`main`/`skiasharp`) and `current == target`, so it is -> **NOT a version bump** β€” keep the milestone, soname, and nuget versions unchanged. It may still include -> new APIs / binding changes (regenerate + build + test as normal). A tip merge is large and conflict-heavy -> because the submodule base is well behind google/skia main, so the **verify-upstream-or-reapply** policy -> (Phase 5 / [gotcha #15](.agents/skills/update-skia/references/known-gotchas.md)) is mandatory: for every -> conflicted file, classify each fork patch as *upstreamed* (take upstream's refined form) or *not -> upstreamed* (re-apply our patch on top of upstream) β€” **never blanket `--theirs`/`--ours`, and never -> silently drop a fork patch**. Snapshot the fork patches before merging -> (`git log --oneline $(git merge-base skiasharp upstream/main)..skiasharp`) and account for **every one** -> in the mono/skia PR's "Conflicts resolved" table as upstreamed (``) or re-applied. A patch that is -> neither is a lost patch β€” STOP rather than ship it. +> **Tip merges are large and conflict-heavy** (the submodule base is well behind `main`), so the +> **verify-upstream-or-reapply** policy is mandatory β€” for every conflicted file, classify each fork +> patch as *upstreamed* (take upstream's refined form) or *not upstreamed* (re-apply on top), never a +> blanket `--theirs`/`--ours`, never a silent drop. Full procedure + the mandatory before-merge +> snapshot/audit: **skill Phase 5** and [gotcha #15](.agents/skills/update-skia/references/known-gotchas.md). **Read `.agents/skills/update-skia/SKILL.md` and follow Phases 2-10.** Notes specific to this automated workflow: @@ -259,24 +249,17 @@ Release-line sync: `${{ needs.pre_activation.outputs.is_release }}`. parent-repo change is `cgmanifest.json`'s commit hash. Do NOT advance the milestone. - **Build platform**: use Linux x64 (`dotnet cake --target=externals-linux --arch=x64`). Clang is pre-configured via env vars. This also applies to Phase 10 if a native rebuild is needed. -- **Native build environment is fully provisioned by the workflow** (clang, `libc++-dev`/`libc++abi-dev`, - fontconfig, ninja). You CANNOT install packages (no apt/sudo inside the sandbox, and the firewall blocks - OS package mirrors). Two distinct cases β€” do not conflate them: - - **Host/flag hacks to silence an error β€” FORBIDDEN.** Do NOT change compiler/linker flags (e.g. - `-stdlib=libc++`) or touch `scripts/infra/native/**` to work around a build error on this Linux host. - Those files are shared across every platform you do NOT build here (Windows/macOS/iOS/Android/WASM), so a - host-specific tweak silently breaks real CI. If a build fails because a host dependency is missing, that - is a workflow bug: STOP, do not hack the build, and record it under "items needing human attention" so the - `Install native build dependencies` step can be fixed. - - **A new *required* upstream gn arg β€” ALLOWED, via `build.cake`.** If the upstream merge introduces a new - gn argument that the build genuinely cannot succeed without (e.g. a new dependency our fork does not vendor, - such as `skia_use_partition_alloc=false` when `third_party/externals/partition_alloc` is disabled in our - `DEPS`), add that gn arg to the affected platforms' `native/**/build.cake` gn-args lists β€” that file is the - single source of truth and belongs next to the existing `skia_use_*` toggles. Do NOT rely on a one-off - `--gnArgs` CLI flag for durable config. Because you only build Linux x64 here, apply the same arg to every - clang platform's `build.cake` that needs it and call the change out prominently in BOTH PR summaries under - "items needing human attention" for cross-platform human review. This is only for a genuinely required - arg β€” never to silence a host-specific error (see the FORBIDDEN case above). +- **Native build environment is provisioned by the host workflow** (clang, `libc++-dev`/`libc++abi-dev`, + fontconfig, ninja). You CANNOT install packages (no apt/sudo in the sandbox; the firewall blocks OS + mirrors). If a build fails because a **host dependency is missing**, that's a workflow bug: STOP, do + NOT hack compiler/linker flags (e.g. `-stdlib=libc++`) or `scripts/infra/native/**` to silence it + (those are shared with the Windows/macOS/iOS/Android/WASM builds you never run here), and record it + under "items needing human attention" so the `Install native build dependencies` step can be fixed. +- **A genuinely required new upstream gn arg** (e.g. `skia_use_partition_alloc=false`, when a new + dependency our `DEPS` doesn't vendor forces it) goes in `native/**/build.cake`, NOT a one-off + `--gnArgs` flag β€” see **[skill Phase 7](.agents/skills/update-skia/SKILL.md) / [gotcha #23](.agents/skills/update-skia/references/known-gotchas.md)**. + Since you only build Linux x64 here, apply it to every clang platform's `build.cake` that needs it and + flag the change in BOTH PR summaries for cross-platform human review. - **NEVER run `externals-download`** in this workflow β€” not even for debugging or baseline comparison. Build from source only. - **Phase 9 reminder**: a green C# build is NOT sufficient - run the new-function diff check from Phase 9 step 1. - **Phase 11 β€” do NOT execute it.** Replace it entirely with the file writes below. From ff1353a7dd67f1b81eb198ca8850a85b7e4ce648 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 22:10:49 +0200 Subject: [PATCH 10/11] Apply Opus 4.7 review fixes to update-skia skill flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up on the skill/workflow split (commit 35ee0689b48): an Opus 4.7 read-only review of SKILL.md + auto-skia-sync.md flagged two live bugs and several consistency gaps left over from the release-line/tip-mode refactor. The workflow points the agent straight at named phases/steps in the skill, so a hardcoded `origin/main` there silently does the wrong thing on a release-line or tip sync. Fixes: Blocking (live refactor misses β€” `origin/main` should follow the resolved base): * Phase 9 step 1: diff unwrapped bindings against `origin/{BASE_BRANCH}`, not `origin/main`. * Phase 5 "before proceeding" gate: describe the parent/submodule base as `origin/{BASE_BRANCH}` instead of `origin/main`. Should-fix (sync/consistency): * Phase 5 step 5 source-file verification: parameterize the milestone-pair diff to `$(git merge-base {SKIA_BASE_BRANCH} upstream/{UPSTREAM_REF}).. upstream/{UPSTREAM_REF}` so it works for tip mode too. * Phase 10 test suite: stop leaking the workflow-only `/tmp/gh-aw/agent` path into the standalone skill β€” use `/tmp/skia-test-output.txt` and note that the automated workflow overrides it to the artifact path. Mirror the override note in the workflow's Phase 10 file-writing section. * Phase 1 step 5: restructure the three modes (main target / release-line target / `main` tip) into clearly-labeled blocks; the closing "use these values" line now lists `{UPSTREAM_REF}` and `chrome/m{TARGET}` so the merge-from ref is covered. Name Phase 2 step 4 as the only milestone-pair diff needing tip substitution (Phase 5 already uses `{UPSTREAM_REF}`). * Phase 4 preamble and Phase 11 branch-target note: add `main`/tip mode to the per-mode branch examples. Body-only edits: `gh aw compile auto-skia-sync` = 0 errors and the `.lock.yml` is byte-identical (the lock embeds only frontmatter). Gotchas are intentionally not renumbered per the reviewer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .agents/skills/update-skia/SKILL.md | 68 ++++++++++++++++------------- .github/workflows/auto-skia-sync.md | 3 +- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/.agents/skills/update-skia/SKILL.md b/.agents/skills/update-skia/SKILL.md index 19038e1900a..e6778a0f74b 100644 --- a/.agents/skills/update-skia/SKILL.md +++ b/.agents/skills/update-skia/SKILL.md @@ -152,30 +152,34 @@ E. Ship (Phase 11) | `{SKIA_BASE_BRANCH}` (mono/skia) | `skiasharp` | `release/.{TARGET}.x` | `skiasharp` | | `{HEAD_BRANCH}` (both repos) | `skia-sync/m{TARGET}` | `skia-sync/release-.{TARGET}.x` | `skia-sync/main` | - A release-line update is always a **bug-fix-only sync** (`CURRENT == TARGET`): do NOT bump - the milestone/soname/nuget version in Phase 6 β€” only `cgmanifest.json`'s commit hash changes. + The table's three columns are the three possible sync modes. **`main` target** (the default) + is a normal milestone bump; the other two are special cases: + + **Release-line target** β€” chosen when `{TARGET}` is older than `main`'s milestone AND a + matching `release/.{TARGET}.x` branch exists. Always a **bug-fix-only sync** + (`CURRENT == TARGET`): do NOT bump the milestone/soname/nuget version in Phase 6 β€” only + `cgmanifest.json`'s commit hash changes. > πŸ›‘ **The release branch must already exist in BOTH repos.** Branch *creation* is owned by > the release process (`release-branch` skill), not this skill. If the SkiaSharp > `release/.{TARGET}.x` branch exists but the mono/skia one does NOT, **STOP and fail** β€” > do not create it here. Ask a human to cut the mono/skia release branch first. - **`main` (tip) mode** is a third option, distinct from both targets above: instead of a - `chrome/m{TARGET}` milestone branch, merge the very tip of upstream `google/skia` `main` - (HEAD). It targets the newest line (`{BASE_BRANCH}` = `main`, `{SKIA_BASE_BRANCH}` = - `skiasharp`, `{HEAD_BRANCH}` = `skia-sync/main`, `{UPSTREAM_REF}` = `main`) and `CURRENT == - TARGET`, so it is **NOT a version bump** β€” keep the milestone/soname/nuget versions - unchanged. Unlike a release-line bug-fix sync it MAY still include new upstream API/binding - changes, so regenerate + build + test as normal (Phases 8–10). A tip merge is large and - conflict-heavy because the submodule base is well behind `main`, so the + **`main` (tip) mode** β€” instead of a `chrome/m{TARGET}` milestone branch, merge the very tip of + upstream `google/skia` `main` (HEAD). Like a `main` target it uses `{BASE_BRANCH}` = `main` and + `{SKIA_BASE_BRANCH}` = `skiasharp`, but with `{HEAD_BRANCH}` = `skia-sync/main` and + `{UPSTREAM_REF}` = `main`, and `CURRENT == TARGET`, so it is **NOT a version bump** β€” keep the + milestone/soname/nuget versions unchanged. Unlike a release-line bug-fix sync it MAY still + include new upstream API/binding changes, so regenerate + build + test as normal (Phases 8–10). + A tip merge is large and conflict-heavy because the submodule base is well behind `main`, so the verify-upstream-or-reapply policy (Phase 5 / [gotcha #15](references/known-gotchas.md)) is - mandatory. Because the tip is not a milestone branch, the milestone-to-milestone diffs in - Phase 2 and Phase 5 don't apply directly β€” substitute - `$(git merge-base {SKIA_BASE_BRANCH} upstream/main)..upstream/main` wherever those phases - diff `chrome/m{CURRENT}..chrome/m{TARGET}`. + mandatory. Because the tip is not a milestone branch, the milestone-pair diff in **Phase 2 step 4** + doesn't apply β€” substitute `$(git merge-base {SKIA_BASE_BRANCH} upstream/main)..upstream/main` + (Phase 5's diffs already use `{UPSTREAM_REF}`, so they need no change). - Use these `{BASE_BRANCH}` / `{SKIA_BASE_BRANCH}` / `{HEAD_BRANCH}` values everywhere below in - place of the hardcoded `main` / `skiasharp` / `skia-sync/m{TARGET}` defaults. + Use these `{UPSTREAM_REF}` / `{BASE_BRANCH}` / `{SKIA_BASE_BRANCH}` / `{HEAD_BRANCH}` values + everywhere below in place of the hardcoded `chrome/m{TARGET}` / `main` / `skiasharp` / + `skia-sync/m{TARGET}` defaults. > πŸ›‘ **GATE**: Confirm current milestone, target milestone, the base branch (main vs release line, > with the mono/skia base branch confirmed to exist), and that the upstream branch exists. @@ -245,9 +249,9 @@ milestone numbers and paste your breaking change analysis table. The default exp > ⚠️ **This phase creates BOTH branches before making ANY changes.** You may be on a > workflow branch, a feature branch, or a detached HEAD β€” none of which is the right base. > You MUST branch from `origin/{BASE_BRANCH}` (parent) and `origin/{SKIA_BASE_BRANCH}` -> (submodule). For a `main` update those are `origin/main` and `origin/skiasharp`; for a -> release-line update they are `origin/release/.{TARGET}.x` in both repos -> (see Phase 1 step 5). +> (submodule). For a `main` update (and `main`/tip mode) those are `origin/main` and +> `origin/skiasharp`; for a release-line update they are `origin/release/.{TARGET}.x` in +> both repos (see Phase 1 step 5). **Parent repo (SkiaSharp):** @@ -364,7 +368,7 @@ You should still be inside `externals/skia` from Phase 4. 5. **Source file verification** β€” Check for added/deleted upstream files: ```bash - git diff upstream/chrome/m{CURRENT}..upstream/chrome/m{TARGET} --diff-filter=AD --name-only -- src/ include/ + git diff $(git merge-base {SKIA_BASE_BRANCH} upstream/{UPSTREAM_REF})..upstream/{UPSTREAM_REF} --diff-filter=AD --name-only -- src/ include/ ``` Cross-reference against `BUILD.gn` β€” new source files may need to be added. @@ -376,8 +380,8 @@ You should still be inside `externals/skia` from Phase 4. > ``` > βœ… **Before proceeding to C (Update & Build):** -> - Parent branch is based on `origin/main` -> - Submodule is at the SHA referenced by the parent's `origin/main` submodule pointer +> - Parent branch is based on `origin/{BASE_BRANCH}` +> - Submodule is at the SHA referenced by the parent's `origin/{BASE_BRANCH}` submodule pointer > - Upstream merge committed with proper two-parent history > - C API files intact, zero conflict markers @@ -504,9 +508,9 @@ the build succeeds. 1. **Review new generated bindings for unwrapped functions:** ```bash - git diff origin/main -- binding/SkiaSharp/SkiaApi.generated.cs | grep "^+.*internal static" + git diff origin/{BASE_BRANCH} -- binding/SkiaSharp/SkiaApi.generated.cs | grep "^+.*internal static" ``` - > ⚠️ The `git diff origin/main` may show additional changes beyond new functions (e.g. + > ⚠️ The `git diff origin/{BASE_BRANCH}` may show additional changes beyond new functions (e.g. > struct renames, type changes from Phase 7 shim work). These are expected and correct. > Only investigate `+internal static` lines β€” ignore other diff noise. @@ -558,13 +562,15 @@ dotnet build binding/SkiaSharp/SkiaSharp.csproj > you missed a version update β€” go back to Phase 6 and verify ALL version lines. > Do NOT work around this with `--no-incremental` or by copying native libs manually. -2. **Full test suite (required before any PR):** +2. **Full test suite (required before any PR):** capture the output to a log you can inspect + afterward. Standalone runs can use any writable path; the automated workflow overrides this to + `/tmp/gh-aw/agent/test-output.txt` so it's uploaded as an artifact (see the workflow's Phase 10 note). ```bash - dotnet test tests/SkiaSharp.Tests.Console/SkiaSharp.Tests.Console.csproj 2>&1 | tee /tmp/gh-aw/agent/test-output.txt + dotnet test tests/SkiaSharp.Tests.Console/SkiaSharp.Tests.Console.csproj 2>&1 | tee /tmp/skia-test-output.txt ``` Wait for it to finish (takes 5–7 min). Then read the summary: ```bash - tail -5 /tmp/gh-aw/agent/test-output.txt + tail -5 /tmp/skia-test-output.txt ``` The last line will look like: `Passed! - Failed: 0, Passed: 5435, Skipped: 171, Total: 5606` @@ -576,7 +582,7 @@ dotnet build binding/SkiaSharp/SkiaSharp.csproj > while tests are still running. Capture with `tee` first, wait for completion, then `tail` > the output file. After the run, inspect failures with: > ```bash - > grep '^ Failed' /tmp/gh-aw/agent/test-output.txt + > grep '^ Failed' /tmp/skia-test-output.txt > ``` Smoke tests are just that β€” smoke. They verify the basics. The full suite MUST pass @@ -601,10 +607,10 @@ before the update can be considered complete. Do not create PRs with only smoke > `[skia-sync] Merge upstream chrome/m{TARGET} bug fixes` instead of milestone-bump titles. > A release-line update is always such a sync. -> **Release-line targets:** Use the `{BASE_BRANCH}` / `{SKIA_BASE_BRANCH}` / `{HEAD_BRANCH}` +> **Branch targets by mode:** Use the `{BASE_BRANCH}` / `{SKIA_BASE_BRANCH}` / `{HEAD_BRANCH}` > values resolved in Phase 1 step 5. For a `main` update they are `main` / `skiasharp` / -> `skia-sync/m{TARGET}`; for a release-line update they are `release/.{TARGET}.x` (both -> repos) / `skia-sync/release-.{TARGET}.x`. +> `skia-sync/m{TARGET}`; for `main`/tip mode `main` / `skiasharp` / `skia-sync/main`; for a +> release-line update `release/.{TARGET}.x` (both repos) / `skia-sync/release-.{TARGET}.x`. #### PR 1: mono/skia (submodule) diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 2d3f5c38e8d..13d99a8ddc9 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -291,7 +291,8 @@ After Phase 10, write these files: - Breaking change analysis, version/binding updates, C# changes, build/test results, items needing human attention All files written to `/tmp/gh-aw/agent/` are automatically uploaded as workflow artifacts. -Write test output there too (`/tmp/gh-aw/agent/test-output.txt`) so failures can be inspected after the run. +For Phase 10, write the test-output log to `/tmp/gh-aw/agent/test-output.txt` (in place of the +skill's default path) so it's uploaded as an artifact and failures can be inspected after the run. Commit submodule changes inside `externals/skia` on `${{ needs.pre_activation.outputs.head_branch }}`. Commit parent repo changes on `${{ needs.pre_activation.outputs.head_branch }}` in the parent. From 36d517d7547b1f4afa207d85c3572d5679338cfe Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Sat, 27 Jun 2026 00:44:40 +0200 Subject: [PATCH 11/11] [skia-sync] Give the agent an honest non-no-op completion signal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A successful upstream-sync run was being mislabeled a "no-op" in the gh-aw conclusion job, and the job then tried (and failed) to file a "no-op runs" issue. Both were misleading: the run had actually merged upstream Skia, built, tested, and produced two real PRs. The cause is the two-subsystem split this workflow uses. Real GitHub writes (push both repos, open both PRs) happen in the custom post-step `skia-sync-push-prs.sh` with SKIASHARP_AUTOBUMP_TOKEN β€” gh-aw itself can't open the mono/skia PR because the submodule's merge commits live in a nested repo it only sees as a gitlink, and `safe-outputs: staged: true` deliberately blocks the agent from creating anything directly. With only `noop` available as a completion signal, every run β€” including real syncs β€” signalled `noop`, and the conclusion job classified on that alone. Give the agent an honest signal instead: * Declare `create-pull-request` in safe-outputs, kept STAGED (preview-only: no real PR, no branch push, no patch β€” the safe_outputs job runs on ubuntu-slim with `permissions: {}` and never touches the workspace). The agent calls it as its completion signal when it did work, so the run registers as a pull-request output rather than a no-op. Real PRs are still opened only by the post-step. * Set `noop: report-as-issue: false` (the gh-aw default is true). `noop` is now reserved for genuine no-work runs and no longer tries to file an issue the workflow has no `issues: write` permission to create. * Update the prompt's "Completion signal" guidance: call `create_pull_request` when `skia-sync-env.sh` was written, `noop` only when it was not. `create_issue` is no longer offered to the agent (it was never used β€” the prompt already forbade it). `gh aw compile` = 0 errors; the 4 pre-existing warnings (3 fixed-schedule + storage.googleapis.com) are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-skia-sync.lock.yml | 70 +++++++++++++---------- .github/workflows/auto-skia-sync.md | 42 ++++++++++++-- 2 files changed, 76 insertions(+), 36 deletions(-) diff --git a/.github/workflows/auto-skia-sync.lock.yml b/.github/workflows/auto-skia-sync.lock.yml index 11f1ce6606b..0ca0da87d20 100644 --- a/.github/workflows/auto-skia-sync.lock.yml +++ b/.github/workflows/auto-skia-sync.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ffc4715b79570f240cee5d95f662b42dde8eab167e8da2d2ca9a498f8cf53122","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fb2388bb266b109c5727d03cc4df6bff13263ccfd846bd1114b7e697d96e957b","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","SKIASHARP_AUTOBUMP_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -236,23 +236,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9a46b533a7ecc461_EOF' + cat << 'GH_AW_PROMPT_011236f10bc2c8a3_EOF' - GH_AW_PROMPT_9a46b533a7ecc461_EOF + GH_AW_PROMPT_011236f10bc2c8a3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_9a46b533a7ecc461_EOF' + cat << 'GH_AW_PROMPT_011236f10bc2c8a3_EOF' - Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_9a46b533a7ecc461_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_9a46b533a7ecc461_EOF' + Tools: create_pull_request, missing_tool, missing_data, noop + GH_AW_PROMPT_011236f10bc2c8a3_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat << 'GH_AW_PROMPT_011236f10bc2c8a3_EOF' - GH_AW_PROMPT_9a46b533a7ecc461_EOF + GH_AW_PROMPT_011236f10bc2c8a3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9a46b533a7ecc461_EOF' + cat << 'GH_AW_PROMPT_011236f10bc2c8a3_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -284,12 +284,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_9a46b533a7ecc461_EOF + GH_AW_PROMPT_011236f10bc2c8a3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_9a46b533a7ecc461_EOF' + cat << 'GH_AW_PROMPT_011236f10bc2c8a3_EOF' {{#runtime-import .github/workflows/auto-skia-sync.md}} - GH_AW_PROMPT_9a46b533a7ecc461_EOF + GH_AW_PROMPT_011236f10bc2c8a3_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -558,46 +558,54 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_e521f6ba33675018_EOF' - {"create_issue":{"labels":["auto-skia-sync"],"max":1,"title_prefix":"[auto-skia-sync]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_e521f6ba33675018_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1c0d66bd1e4f6d52_EOF' + {"create_pull_request":{"if_no_changes":"ignore","max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"staged":true},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_1c0d66bd1e4f6d52_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[auto-skia-sync]\". Labels [\"auto-skia-sync\"] will be automatically added." + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created." }, "repo_params": {}, "dynamic_tools": [] } GH_AW_VALIDATION_JSON: | { - "create_issue": { + "create_pull_request": { "defaultMax": 1, "fields": { + "base": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, "body": { "required": true, "type": "string", "sanitize": true, "maxLength": 65000 }, + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" + }, "labels": { "type": "array", "itemType": "string", "itemSanitize": true, "itemMaxLength": 128 }, - "parent": { - "issueOrPRNumber": true - }, "repo": { "type": "string", "maxLength": 256 }, - "temporary_id": { - "type": "string" - }, "title": { "required": true, "type": "string", @@ -758,7 +766,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_aeac60b37cf728ae_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_46ab8df01cc8e83e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -806,7 +814,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_aeac60b37cf728ae_EOF + GH_AW_MCP_CONFIG_46ab8df01cc8e83e_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1087,7 +1095,7 @@ jobs: GH_AW_WORKFLOW_NAME: "Skia Upstream Sync" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_NOOP_REPORT_AS_ISSUE: "false" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1158,6 +1166,8 @@ jobs: GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" @@ -1444,8 +1454,8 @@ jobs: code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} + created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1491,7 +1501,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,android.googlesource.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,chrome-infra-packages.appspot.com,chromium.googlesource.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dawn.googlesource.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gn.googlesource.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,skia.googlesource.com,storage.googleapis.com,swiftshader.googlesource.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"auto-skia-sync\"],\"max\":1,\"title_prefix\":\"[auto-skia-sync]\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"if_no_changes\":\"ignore\",\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"staged\":true},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" GH_AW_SAFE_OUTPUTS_STAGED: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/auto-skia-sync.md b/.github/workflows/auto-skia-sync.md index 13d99a8ddc9..b26ddae2cec 100644 --- a/.github/workflows/auto-skia-sync.md +++ b/.github/workflows/auto-skia-sync.md @@ -126,10 +126,25 @@ permissions: pull-requests: read # -- Safe outputs ------------------------------------------------------ -# All GitHub writes (push, PR) are done in post-steps via bash. -# Stage all safe outputs so the agent can't create issues/PRs directly. +# All real GitHub writes (push, both PRs) are done in the post-step via bash with +# SKIASHARP_AUTOBUMP_TOKEN β€” gh-aw can't create the mono/skia PR (the submodule's merge +# commits live in a nested repo gh-aw sees only as a gitlink) and `staged: true` keeps the +# agent from creating anything directly. +# +# `create-pull-request` is declared ONLY as an honest completion signal: it is kept STAGED +# (preview-only β€” NO real PR is created), so a successful sync registers as a pull-request +# output instead of being mislabeled a "no-op". The agent calls it when work was done and +# `noop` only when there genuinely was none. safe-outputs: staged: true + create-pull-request: + staged: true + if-no-changes: ignore + # report-as-issue defaults to true, but this workflow has no `issues: write` and a real sync + # is NOT a no-op β€” disable the no-opβ†’issue posting so genuine no-work runs don't try (and fail) + # to file a "no-op runs" issue. + noop: + report-as-issue: false # -- Sandbox ----------------------------------------------------------- # Mount host fontconfig config AND font files into the AWF chroot. @@ -263,10 +278,13 @@ Release-line sync: `${{ needs.pre_activation.outputs.is_release }}`. - **NEVER run `externals-download`** in this workflow β€” not even for debugging or baseline comparison. Build from source only. - **Phase 9 reminder**: a green C# build is NOT sufficient - run the new-function diff check from Phase 9 step 1. - **Phase 11 β€” do NOT execute it.** Replace it entirely with the file writes below. - Do NOT push branches, create PRs, or create issues β€” all GitHub artifacts are handled by the post-step. - Just commit locally. Do NOT call `create_issue` or `create_pull_request`. -- **"No work" signal**: the pre-activation step skips the workflow when there are no new upstream commits, - so this should not happen. If somehow it does, do NOT write `skia-sync-env.sh` and stop. + Do NOT push branches or create *real* PRs/issues β€” every GitHub artifact is created by the + post-step (it pushes both repos and opens both PRs with the autobump token). Just commit locally. + Do NOT call `create_issue`. Your completion signal is `create_pull_request` (staged) when you did + work, or `noop` when you did not β€” see "Completion signal" at the end of this prompt. +- **"No work" signal**: the pre-activation step skips the workflow when there are no new upstream + commits, so this should rarely happen. If it does, do NOT write `skia-sync-env.sh`, call `noop` + with a one-line reason, and stop. After Phase 10, write these files: @@ -296,3 +314,15 @@ skill's default path) so it's uploaded as an artifact and failures can be inspec Commit submodule changes inside `externals/skia` on `${{ needs.pre_activation.outputs.head_branch }}`. Commit parent repo changes on `${{ needs.pre_activation.outputs.head_branch }}` in the parent. + +## Completion signal + +When the sync is done β€” you have committed locally and written `skia-sync-env.sh` plus both +summaries β€” call the `create_pull_request` safe-output tool **once** as your completion signal. +It is **staged** (preview-only: it creates NO real PR and pushes nothing β€” the post-step opens both +real PRs with the autobump token), but it records this run as a real upstream sync instead of a +no-op. Pass a short title (the `[skia-sync] …` title for this mode) and a one-line body pointing at +the two summary files. Do this **instead of** `noop`. + +Call `noop` (and never `create_pull_request`) **only** when there was genuinely no work to do +β€” i.e. you did not write `skia-sync-env.sh`.