Skip to content

Merge pull request #266 from tannergooding/tannergooding-layer2-core #16

Merge pull request #266 from tannergooding/tannergooding-layer2-core

Merge pull request #266 from tannergooding/tannergooding-layer2-core #16

name: regenerate-native
# Regenerates the native runtime packages:
# * libLLVM.runtime.* (the prebuilt shared libLLVM lifted per platform)
# * libLLVMSharp.runtime.* (our helper library, compiled against the matching LLVM release)
#
# libLLVM regenerates when the tracked LLVM version changes (the version in the
# top-level CMakeLists.txt, which maps to the llvmorg-<version> release tag). libLLVMSharp
# regenerates for that same reason or whenever sources/libLLVMSharp changes. Both can also be
# forced via workflow_dispatch. See the "Regenerating native binaries" section in README.md.
#
# Unlike ClangSharp (which lifts the shared libclang shipped for every platform), the official
# LLVM releases only ship a shared libLLVM on Windows (as LLVM-C.dll). Linux and macOS are
# therefore lifted from the most-official prebuilt source per platform: apt.llvm.org (the LLVM
# project's own Debian/Ubuntu repository) on Linux and Homebrew on macOS. This is why libLLVM
# runs as a per-runtime matrix on native runners rather than on a single Windows runner.
#
# The jobs upload the resulting .nupkg files as build artifacts and sign them via the
# separate sign-nuget leg; publishing to NuGet is manual.
on:
push:
branches: [ main ]
workflow_dispatch:
inputs:
libllvm:
description: "Force regenerating the libLLVM runtime packages"
type: boolean
default: false
libllvmsharp:
description: "Force regenerating the libLLVMSharp runtime packages"
type: boolean
default: false
permissions:
contents: read
concurrency:
group: regenerate-native-${{ github.ref }}
cancel-in-progress: true
jobs:
detect:
runs-on: ubuntu-latest
outputs:
llvm-version: ${{ steps.detect.outputs.llvm-version }}
libllvm: ${{ steps.detect.outputs.libllvm }}
libllvmsharp: ${{ steps.detect.outputs.libllvmsharp }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: detect
shell: bash
run: |
set -euo pipefail
parse_version() { sed -n 's/^project(LLVMSharp VERSION \([0-9.]*\)).*/\1/p' "$1"; }
# The top-level CMakeLists.txt is the source of truth for the tracked LLVM version.
version="$(parse_version CMakeLists.txt)"
if [ -z "${version}" ]; then echo "could not parse LLVM version from CMakeLists.txt" >&2; exit 1; fi
echo "llvm-version=${version}" >> "$GITHUB_OUTPUT"
libllvm=false
libllvmsharp=false
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
if [ "${{ inputs.libllvm }}" = "true" ]; then libllvm=true; fi
if [ "${{ inputs.libllvmsharp }}" = "true" ]; then libllvmsharp=true; fi
else
before="${{ github.event.before }}"
if [ -z "${before}" ] || [ "${before}" = "0000000000000000000000000000000000000000" ] || ! git cat-file -e "${before}^{commit}" 2>/dev/null; then
# No usable baseline to diff against (new/recreated branch, force-push,
# unfetched history); regenerate everything conservatively.
libllvm=true
libllvmsharp=true
else
# libLLVM regenerates when the tracked LLVM version changes. The libLLVM package
# version tracks the full patch (e.g. 21.1.8), so compare the full version.
prev_version="$(git show "${before}:CMakeLists.txt" 2>/dev/null | sed -n 's/^project(LLVMSharp VERSION \([0-9.]*\)).*/\1/p')"
if [ "${version}" != "${prev_version}" ]; then
libllvm=true
fi
# libLLVMSharp regenerates for the same reason or when its sources change.
if [ "${libllvm}" = "true" ] || ! git diff --quiet "${before}" HEAD -- sources/libLLVMSharp/; then
libllvmsharp=true
fi
fi
fi
echo "libllvm=${libllvm}" >> "$GITHUB_OUTPUT"
echo "libllvmsharp=${libllvmsharp}" >> "$GITHUB_OUTPUT"
echo "Resolved: llvm=${version} (major.minor=${major_minor}) libllvm=${libllvm} libllvmsharp=${libllvmsharp}"
- name: Verify package versions match the tracked LLVM version
# Guards against bumping CMakeLists.txt without updating the nuspec/runtime.json
# versions. libLLVM versions must equal the LLVM version exactly; libLLVMSharp
# versions must be that version plus an independent build revision (e.g. 21.1.8.1).
shell: bash
run: |
set -euo pipefail
version="${{ steps.detect.outputs.llvm-version }}"
rc=0
nuspec_version() { sed -n 's:.*<version>\([^<]*\)</version>.*:\1:p' "$1" | head -n1; }
nuspec_branch() { sed -n 's:.*<repository[^>]*branch="\([^"]*\)".*:\1:p' "$1" | head -n1; }
json_versions() { grep -oE '"[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?"' "$1" | tr -d '"'; }
fail() { echo "::error file=$1::$2"; rc=1; }
if [ "${{ steps.detect.outputs.libllvm }}" = "true" ]; then
for f in packages/libLLVM/libLLVM/libLLVM.nuspec packages/libLLVM/libLLVM.runtime.*/*.nuspec; do
v="$(nuspec_version "$f")"
[ "${v}" = "${version}" ] || fail "$f" "version '${v}' does not match LLVM version '${version}'"
b="$(nuspec_branch "$f")"
if [ -n "${b}" ] && [ "${b}" != "llvmorg-${version}" ]; then
fail "$f" "repository branch '${b}' does not match 'llvmorg-${version}'"
fi
done
for v in $(json_versions packages/libLLVM/libLLVM/runtime.json); do
[ "${v}" = "${version}" ] || fail "packages/libLLVM/libLLVM/runtime.json" "mapped version '${v}' does not match LLVM version '${version}'"
done
fi
if [ "${{ steps.detect.outputs.libllvmsharp }}" = "true" ]; then
for f in packages/libLLVMSharp/libLLVMSharp/libLLVMSharp.nuspec packages/libLLVMSharp/libLLVMSharp.runtime.*/*.nuspec; do
v="$(nuspec_version "$f")"
case "${v}" in
"${version}".*) : ;;
*) fail "$f" "version '${v}' is not 'llvm-version.<revision>' (expected '${version}.<n>')" ;;
esac
done
for v in $(json_versions packages/libLLVMSharp/libLLVMSharp/runtime.json); do
case "${v}" in
"${version}".*) : ;;
*) fail "packages/libLLVMSharp/libLLVMSharp/runtime.json" "mapped version '${v}' is not '${version}.<revision>'" ;;
esac
done
fi
if [ "${rc}" -ne 0 ]; then
echo "Update the package versions to match the tracked LLVM version (${version}) before regenerating." >&2
exit 1
fi
echo "Package versions verified against LLVM ${version}"
# -------- libLLVM: lift the shared libLLVM from the most-official prebuilt source --------
libllvm-build:
needs: detect
if: ${{ needs.detect.outputs.libllvm == 'true' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { rid: win-x64, os: windows-latest }
- { rid: win-arm64, os: windows-11-arm }
- { rid: linux-x64, os: ubuntu-latest }
- { rid: linux-arm64, os: ubuntu-24.04-arm }
- { rid: osx-arm64, os: macos-latest }
steps:
- uses: actions/checkout@v4
- name: Lift libLLVM (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/build.ps1 -regeneratenative -target libLLVM -rid ${{ matrix.rid }}
- name: Lift libLLVM (Unix)
if: runner.os != 'Windows'
shell: bash
run: ./scripts/build.sh --regeneratenative --target libLLVM --rid ${{ matrix.rid }}
- uses: actions/upload-artifact@v4
with:
name: libllvm-native-${{ matrix.rid }}
path: artifacts/native/${{ matrix.rid }}/*
if-no-files-found: error
libllvm-pack:
needs: [ detect, libllvm-build ]
if: ${{ needs.detect.outputs.libllvm == 'true' }}
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: NuGet/setup-nuget@v2
- uses: actions/download-artifact@v4
with:
pattern: libllvm-native-*
path: staging
- name: Pack libLLVM packages
shell: bash
run: |
set -euo pipefail
mkdir -p out
for rid in win-x64 win-arm64 linux-x64 linux-arm64 osx-arm64; do
cp staging/"libllvm-native-${rid}"/* "packages/libLLVM/libLLVM.runtime.${rid}/"
nuget pack "packages/libLLVM/libLLVM.runtime.${rid}/libLLVM.runtime.${rid}.nuspec" -OutputDirectory out
done
nuget pack "packages/libLLVM/libLLVM/libLLVM.nuspec" -OutputDirectory out
- uses: actions/upload-artifact@v4
with:
name: libLLVM-packages
path: out/*.nupkg
if-no-files-found: error
# -------- libLLVMSharp: compile our helper against that same LLVM release --------
libllvmsharp-build:
needs: detect
if: ${{ needs.detect.outputs.libllvmsharp == 'true' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { rid: win-x64, os: windows-latest }
- { rid: win-arm64, os: windows-11-arm }
- { rid: linux-x64, os: ubuntu-latest }
- { rid: linux-arm64, os: ubuntu-24.04-arm }
- { rid: osx-arm64, os: macos-latest }
steps:
- uses: actions/checkout@v4
- name: Build libLLVMSharp (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/build.ps1 -regeneratenative -target libLLVMSharp -rid ${{ matrix.rid }}
- name: Build libLLVMSharp (Unix)
if: runner.os != 'Windows'
shell: bash
run: ./scripts/build.sh --regeneratenative --target libLLVMSharp --rid ${{ matrix.rid }}
- uses: actions/upload-artifact@v4
with:
name: libllvmsharp-native-${{ matrix.rid }}
path: artifacts/native/${{ matrix.rid }}/*
if-no-files-found: error
libllvmsharp-pack:
needs: [ detect, libllvmsharp-build ]
if: ${{ needs.detect.outputs.libllvmsharp == 'true' }}
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: NuGet/setup-nuget@v2
- uses: actions/download-artifact@v4
with:
pattern: libllvmsharp-native-*
path: staging
- name: Pack libLLVMSharp packages
shell: bash
run: |
set -euo pipefail
mkdir -p out
for rid in win-x64 win-arm64 linux-x64 linux-arm64 osx-arm64; do
cp staging/"libllvmsharp-native-${rid}"/* "packages/libLLVMSharp/libLLVMSharp.runtime.${rid}/"
nuget pack "packages/libLLVMSharp/libLLVMSharp.runtime.${rid}/libLLVMSharp.runtime.${rid}.nuspec" -OutputDirectory out
done
nuget pack "packages/libLLVMSharp/libLLVMSharp/libLLVMSharp.nuspec" -OutputDirectory out
- uses: actions/upload-artifact@v4
with:
name: libLLVMSharp-packages
path: out/*.nupkg
if-no-files-found: error
# -------- sign the native packages (separate leg, native-specific file list) --------
sign-nuget:
needs: [ libllvm-pack, libllvmsharp-pack ]
# Sign on pushes to main whenever the legs that actually ran succeeded. A leg that
# was version-gated out is 'skipped' (not a failure), so a libLLVMSharp-only
# regeneration still gets signed; a real failure or cancellation blocks signing.
if: >-
${{ github.event_name == 'push'
&& !cancelled()
&& needs.libllvm-pack.result != 'failure'
&& needs.libllvmsharp-pack.result != 'failure'
&& (needs.libllvm-pack.result == 'success' || needs.libllvmsharp-pack.result == 'success') }}
runs-on: windows-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
merge-multiple: true
path: ./artifacts/pkg
pattern: "lib*-packages"
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json
- run: dotnet tool install --tool-path ./artifacts/tools --prerelease sign
- uses: azure/login@v2
with:
allow-no-subscriptions: true
client-id: "${{ secrets.AZURE_CLIENT_ID }}"
tenant-id: "${{ secrets.AZURE_TENANT_ID }}"
- run: ./artifacts/tools/sign code azure-key-vault "**/*.nupkg" --base-directory "${{ github.workspace }}/artifacts/pkg" --file-list "${{ github.workspace }}/scripts/SignClientFileListNative.txt" --publisher-name ".NET Foundation" --description "LLVMSharp" --description-url "https://github.com/dotnet/llvmsharp" --azure-credential-type "azure-cli" --azure-key-vault-url "${{ secrets.KEY_VAULT_URL }}" --azure-key-vault-certificate "${{ secrets.KEY_VAULT_CERTIFICATE_ID }}"
shell: pwsh
- uses: actions/upload-artifact@v4
with:
name: sign_nuget_native
path: |
./artifacts/pkg/**/*
if-no-files-found: error