Skip to content

Release Clang Tools #12

Release Clang Tools

Release Clang Tools #12

name: Release Clang Tools
on:
push:
# Only the default branch, so pushing a work branch cannot publish releases.
branches: [master]
schedule:
# Run every 6 hours to check for new LLVM tags
- cron: '0 */6 * * *'
workflow_dispatch:
inputs:
llvm_tag:
description: 'Specific LLVM tag(s) to process, space or comma separated (optional)'
required: false
type: string
force:
description: 'Reprocess tags that already have a release and upload any missing binaries. With no llvm_tag this backfills every known tag.'
required: false
default: false
type: boolean
permissions:
contents: write
jobs:
check-and-release:
runs-on: ubuntu-latest
outputs:
has_new_tags: ${{ steps.llvm-tags.outputs.has_new_tags }}
tags: ${{ steps.llvm-tags.outputs.tags }}
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Get LLVM tags
id: llvm-tags
env:
INPUT_TAG: ${{ github.event.inputs.llvm_tag }}
INPUT_FORCE: ${{ github.event.inputs.force }}
run: |
set -euo pipefail
# All upstream release tags from llvmorg-14.0.0 onwards (binary availability
# became consistent there), excluding release candidates.
git ls-remote --tags https://github.com/llvm/llvm-project.git |
grep 'refs/tags/llvmorg-[0-9]' |
grep -v '\^{}' |
sed 's/.*refs\/tags\///' |
grep -E '^llvmorg-(1[4-9]|[2-9][0-9])\.' |
grep -v -- '-rc[0-9]' |
sort -V -u > all_llvm_tags.txt
git tag | grep -E '^llvmorg-(1[4-9]|[2-9][0-9])\.' | grep -v -- '-rc[0-9]' |
sort -V -u > existing_tags.txt || : > existing_tags.txt
echo "Upstream tags: $(wc -l < all_llvm_tags.txt)"
echo "Tags already released here: $(wc -l < existing_tags.txt)"
if [ -n "${INPUT_TAG}" ]; then
# Explicit list wins; accept commas or whitespace as separators.
echo "${INPUT_TAG}" | tr ',' '\n' | tr -s '[:space:]' '\n' |
sed '/^$/d' > selected_tags.txt
elif [ "${INPUT_FORCE}" = "true" ]; then
# Backfill mode: revisit everything so releases missing binaries get fixed.
cp all_llvm_tags.txt selected_tags.txt
else
# grep -vxF compares whole lines via a hash, so neither input has to be
# sorted; comm would silently misbehave on `sort -V` output.
grep -vxF -f existing_tags.txt all_llvm_tags.txt > selected_tags.txt || : > selected_tags.txt
fi
echo "Tags selected for processing:"
cat selected_tags.txt
# A matrix is capped at 256 jobs.
if [ "$(wc -l < selected_tags.txt)" -gt 256 ]; then
echo "::warning::More than 256 tags selected; processing the first 256."
head -256 selected_tags.txt > selected_tags.trimmed && mv selected_tags.trimmed selected_tags.txt
fi
if [ -s selected_tags.txt ]; then
echo "tags=$(jq -R -s -c 'split("\n")[:-1]' < selected_tags.txt)" >> "$GITHUB_OUTPUT"
echo "has_new_tags=true" >> "$GITHUB_OUTPUT"
else
echo "has_new_tags=false" >> "$GITHUB_OUTPUT"
fi
create-releases:
needs: check-and-release
if: needs.check-and-release.outputs.has_new_tags == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
llvm_tag: ${{ fromJson(needs.check-and-release.outputs.tags) }}
max-parallel: 5
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Check whether ${{ matrix.llvm_tag }} needs processing
id: precheck
env:
LLVM_TAG: ${{ matrix.llvm_tag }}
INPUT_FORCE: ${{ github.event.inputs.force }}
run: |
set -euo pipefail
if git rev-parse -q --verify "refs/tags/${LLVM_TAG}" >/dev/null && [ "${INPUT_FORCE}" != "true" ]; then
echo "Tag ${LLVM_TAG} already exists; rerun with force to refresh its binaries."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Free up disk space
if: steps.precheck.outputs.skip != 'true'
run: |
# The LLVM archives reach ~2 GB each and the runner image ships with
# several GB of toolchains this job never touches.
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
df -h /
- name: Download clang-format and clang-tidy for ${{ matrix.llvm_tag }}
id: fetch
if: steps.precheck.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LLVM_TAG: ${{ matrix.llvm_tag }}
run: |
set +e
bash ./scripts/fetch-clang-tools.sh "${LLVM_TAG}" "artifacts/${LLVM_TAG}"
rc=$?
set -e
# Exit code 3 means upstream has not published this release yet. Not an
# error: leave the tag unprocessed so a later run picks it up.
if [ "${rc}" -eq 3 ]; then
echo "nothing_to_publish=true" >> "$GITHUB_OUTPUT"
exit 0
fi
exit "${rc}"
- name: Publish release ${{ matrix.llvm_tag }}
if: steps.precheck.outputs.skip != 'true' && steps.fetch.outputs.nothing_to_publish != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LLVM_TAG: ${{ matrix.llvm_tag }}
run: |
set -euo pipefail
VERSION=${LLVM_TAG#llvmorg-}
ARTIFACTS="artifacts/${LLVM_TAG}"
# Describe what this release actually contains rather than what it might.
{
echo "Standalone clang-format and clang-tidy binaries extracted from LLVM ${VERSION}."
echo
echo "Download the binary for your platform and make it executable (\`chmod +x\` on Unix systems)."
echo
echo "| Binary | Size |"
echo "| --- | --- |"
for binary in "${ARTIFACTS}"/*; do
echo "| \`$(basename "${binary}")\` | $(du -h "${binary}" | cut -f1) |"
done
echo
echo "Platforms absent from this list were not published by upstream for this version."
echo
echo "Source: https://github.com/llvm/llvm-project/releases/tag/${LLVM_TAG}"
} > release_notes.md
# No `git tag` + `git push` here: `gh release create` creates the tag at
# --target when it does not exist yet, which keeps this working with
# persist-credentials disabled.
if gh release view "${LLVM_TAG}" >/dev/null 2>&1; then
echo "Updating existing release ${LLVM_TAG}"
gh release edit "${LLVM_TAG}" \
--title "clang-format and clang-tidy ${VERSION}" \
--notes-file release_notes.md
else
gh release create "${LLVM_TAG}" \
--title "clang-format and clang-tidy ${VERSION}" \
--notes-file release_notes.md \
--target master
fi
# --clobber so a rerun repairs a release that is missing binaries.
gh release upload "${LLVM_TAG}" "${ARTIFACTS}"/* --clobber
echo "Published $(ls -1 "${ARTIFACTS}" | wc -l) binaries for ${LLVM_TAG}"