blob: f0e55f172451e91d40e534ca26c2832c94e1d1f8 [file] [edit]
# Copyright (C) 2026 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# One manual step that finalizes a release. It:
# 1. Attaches the LUCI-built binaries + SDK source zips to the draft release.
# 2. Rolls the prebuilt manifests to this version (tools/release/roll-prebuilts)
# and opens a PR to main. roll-prebuilts also stamps the agent-skills
# version, so the prebuilts and the bundle move together.
# 3. Assembles the agent-skills bundle (RFC-0026) from the freshly rolled
# trace_processor + this tag's ai/skills, and opens a PR to the ai-agents
# branch (plus a refs/ai-agent-tag/<version> version pin).
#
# Everything human-gated: the draft release and both PRs are reviewed and
# published / merged by a maintainer. Idempotent — safe to re-run; the PR head
# branches are reused and stale PRs are superseded.
#
# Mints a `perfetto-automation` GitHub App token (like promote-stable.yml) so
# the PR open / push events fan out to other workflow runs.
name: Finalize release (artifacts + prebuilt roll + agent skills)
on:
workflow_dispatch:
inputs:
version:
description: 'Release version to finalize (e.g. v54.0)'
required: true
type: string
permissions: {}
jobs:
finalize:
# Self-hosted runners have ambient gcloud credentials, needed by
# install-build-deps to restore the buildtools cache from GCS.
runs-on: self-hosted
environment: perfetto-automation
timeout-minutes: 60
steps:
- name: Validate input
env:
VERSION: ${{ inputs.version }}
run: |
if ! [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+$ ]]; then
echo "::error::Version must match vX.Y (got: $VERSION)"
exit 1
fi
- name: Mint app token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.PERFETTO_BOT_APP_ID }}
private-key: ${{ secrets.PERFETTO_BOT_PRIVATE_KEY }}
# main is the base for the prebuilt-roll PR; tags are needed to pull this
# release's ai/skills into the bundle.
- name: Checkout main
uses: actions/checkout@v5
with:
ref: main
fetch-depth: 0
fetch-tags: true
token: ${{ steps.app-token.outputs.token }}
# Self-hosted runners don't come with the GitHub CLI preinstalled.
- name: Install GitHub CLI
run: |
if ! command -v gh >/dev/null 2>&1; then
# When bumping, re-verify GH_SHA256 against the upstream checksums:
# https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_checksums.txt
GH_VERSION=2.90.0
GH_SHA256=b2aef7b23ec6899bf27f37a32c57a7935d0a178568ac33dc9bb03842f724195a
GH_TARBALL="gh_${GH_VERSION}_linux_amd64.tar.gz"
mkdir -p "$HOME/.local/bin"
curl -fsSLO "https://github.com/cli/cli/releases/download/v${GH_VERSION}/${GH_TARBALL}"
echo "${GH_SHA256} ${GH_TARBALL}" | sha256sum --check --status
tar -xzf "${GH_TARBALL}" --strip-components=2 -C "$HOME/.local/bin" \
"gh_${GH_VERSION}_linux_amd64/bin/gh"
rm "${GH_TARBALL}"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.local/bin:$PATH"
fi
gh --version
- name: Verify draft release exists
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ inputs.version }}
run: |
if ! gh release view "$VERSION" --repo "${{ github.repository }}" \
> /dev/null 2>&1; then
echo "::error::No release found for tag $VERSION. Push the tag first (via promote-stable.yml) to create the draft."
exit 1
fi
- name: Install gcloud / gsutil
uses: google-github-actions/setup-gcloud@v2
- name: Download, verify, and package LUCI artifacts
env:
VERSION: ${{ inputs.version }}
run: |
tools/release/package-github-release-artifacts --yes "$VERSION"
- name: Upload assets to draft release
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ inputs.version }}
run: |
STAGING="/tmp/perfetto-${VERSION}-github-release"
gh release upload "$VERSION" "$STAGING"/*.zip \
--repo "${{ github.repository }}" --clobber
echo "::notice::Artifacts attached. Review the draft release at https://github.com/${{ github.repository }}/releases and publish it manually when ready."
- name: Install build deps
uses: ./.github/actions/install-build-deps
- name: Roll prebuilts
env:
VERSION: ${{ inputs.version }}
run: |
# Updates the prebuilt manifests + regenerates tools/trace_processor to
# this version, and stamps the agent-skills version in lockstep.
tools/release/roll-prebuilts "$VERSION"
- name: Configure git
run: |
# Email must match the CLA-approved address for the automation app.
git config user.name "perfetto-automation[bot]"
git config user.email "perfetto-automation-github-app@google.com"
- name: Open prebuilt-roll PR to main
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ inputs.version }}
run: |
BRANCH="dev/github-bot/roll-prebuilts"
git checkout -q -B "$BRANCH"
git add -A
if git diff --cached --quiet; then
echo "::notice::Prebuilts already current for $VERSION; no roll PR."
else
git commit -q -m "Roll prebuilts to $VERSION"
for N in $(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number'); do
gh pr close "$N" --comment "Superseded by a newer prebuilt roll."
done
git push origin --delete "$BRANCH" 2>/dev/null || true
git push -q -u origin "$BRANCH"
gh pr create \
--base main \
--head "$BRANCH" \
--title "Roll prebuilts to $VERSION" \
--body "Automated prebuilt roll for **$VERSION**: bumps the prebuilt binary manifests (trace_processor, traceconv, tracebox, SDK sources, heapprofd) and stamps the matching agent-skills version (RFC-0026)."
echo "::notice::Opened prebuilt-roll PR for $VERSION."
fi
# Build the bundle from the rolled tree (correct trace_processor + stamped
# version) plus THIS tag's skills, then open a PR to the ai-agents branch.
- name: Publish agent-skills bundle
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ inputs.version }}
REPO: ${{ github.repository }}
run: |
# The bundle ships THIS tag's skills. Check the tag out into its
# own worktree and point the bundler at it, leaving the rolled main
# checkout untouched; everything else (rolled trace_processor,
# stamped manifests + installer) still comes from this checkout.
# Tags from before the single-skill layout (#6156) have no
# bundleable skill; skip the bundle (never substitute main's
# skills) rather than failing the whole finalize run.
git worktree add --detach /tmp/release-tag-tree "$VERSION"
SKILLS_SRC=/tmp/release-tag-tree/ai/skills
if [[ ! -f "$SKILLS_SRC/perfetto/SKILL-template.md" ]]; then
echo "::warning::$VERSION predates the single-skill layout (#6156); skipping the agent-skills bundle."
git worktree remove --force /tmp/release-tag-tree
exit 0
fi
tools/release/build_ai_agents.py --output /tmp/ai-agents-tree \
--skills-src "$SKILLS_SRC"
git worktree remove --force /tmp/release-tag-tree
BRANCH="dev/github-bot/ai-agents-bundle"
REMOTE="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"
# Assemble the bundle commit in a scratch repo, off the ai-agents tip.
WORK=/tmp/ai-agents-branch
rm -rf "$WORK"; mkdir -p "$WORK"; cd "$WORK"
git init -q
git config user.name "perfetto-automation[bot]"
git config user.email "perfetto-automation-github-app@google.com"
git remote add origin "$REMOTE"
if git fetch -q --depth 1 origin ai-agents; then
git checkout -q -B "$BRANCH" FETCH_HEAD
else
echo "::notice::ai-agents branch does not exist yet; seeding an empty base."
git checkout -q --orphan ai-agents
git commit -q --allow-empty -m "Initialize ai-agents branch"
git push -q origin HEAD:refs/heads/ai-agents
git checkout -q -B "$BRANCH"
fi
git rm -rqf . >/dev/null 2>&1 || true
cp -a /tmp/ai-agents-tree/. .
git add -A
if git diff --cached --quiet; then
echo "::notice::ai-agents bundle already current for $VERSION; no PR needed."
exit 0
fi
git commit -q -m "Perfetto $VERSION agent skills bundle"
BUNDLE_SHA=$(git rev-parse HEAD)
# Map the version to this bundle commit (out of refs/tags). The ref
# also keeps the commit fetchable after the head branch is deleted.
git push -q --force origin "$BUNDLE_SHA:refs/ai-agent-tag/$VERSION"
for N in $(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number'); do
gh pr close "$N" --comment "Superseded by a newer ai-agents bundle."
done
git push origin --delete "$BRANCH" 2>/dev/null || true
git push -q origin "HEAD:refs/heads/$BRANCH"
gh pr create \
--base ai-agents \
--head "$BRANCH" \
--title "Update ai-agents skills bundle ($VERSION)" \
--body "$(cat <<EOF
Automated agent-skills bundle for **$VERSION** (RFC-0026): this tag's
skill tree (with the freshly rolled \`trace_processor\` wrapper
bundled inside it at \`plugins/perfetto/skills/perfetto/bin/\`) and
the per-agent manifests.
The version pin \`refs/ai-agent-tag/$VERSION\` already points at this
bundle commit (\`$BUNDLE_SHA\`). On merge, \`ai-agents\` advances and
installed agents pick up the update through their normal channels.
EOF
)"
echo "::notice::Opened ai-agents bundle PR and tagged $VERSION."
# Builds and uploads the perfetto package to PyPI via Trusted Publishing.
# Requires a `pypi` Environment and a PyPI trusted publisher for
# (google/perfetto, finalize-release.yml, env: pypi). See
# https://docs.pypi.org/trusted-publishers/.
publish-pypi:
name: Publish Python package to PyPI
needs: finalize
runs-on: ubuntu-latest
environment: pypi
timeout-minutes: 15
permissions:
id-token: write # OIDC token for Trusted Publishing.
steps:
- name: Validate input
env:
VERSION: ${{ inputs.version }}
run: |
if ! [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+$ ]]; then
echo "::error::Version must match vX.Y (got: $VERSION)"
exit 1
fi
- name: Checkout release tag
uses: actions/checkout@v5
with:
ref: ${{ inputs.version }}
fetch-depth: 1
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Build sdist + wheel
env:
VERSION: ${{ inputs.version }}
REPO: ${{ github.repository }}
run: |
# setup.py derives the version from the CHANGELOG's first vX.Y entry;
# fail early if it doesn't match the tag being published.
TOP_VER="$(grep -m1 -oE '^v[0-9]+\.[0-9]+' CHANGELOG)"
if [[ "$TOP_VER" != "$VERSION" ]]; then
echo "::error::CHANGELOG top release entry ($TOP_VER) != tag $VERSION. The CHANGELOG's first vX.Y entry must be $VERSION."
exit 1
fi
# download_url is PyPI metadata only; point it at this tag's archive.
ARCHIVE="https://github.com/${REPO}/archive/refs/tags/${VERSION}.zip"
sed -i -E "s#download_url\s*=\s*'[^']*'#download_url='${ARCHIVE}'#" \
python/setup.py
python -m pip install --upgrade build
python -m build python
- name: Publish to PyPI (Trusted Publishing)
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: python/dist
skip-existing: true # Safe re-runs: skip a version already on PyPI.