From 4027a4ed67338d67c09ae2108b1dcd8d16566ddd Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 15 Jul 2026 23:02:13 -0700 Subject: [PATCH] Move regenerate-native change detection into build.sh to match ClangSharp The detect job inlined its change-detection and package-version verification in YAML and referenced an unassigned `major_minor` in a diagnostic echo, which under `set -u` aborted the whole regenerate-native workflow. Mirror ClangSharp instead: the change-detection and version-verification logic now lives in scripts/build.sh as DetectChanges/VerifyPackages (invoked via --detectchanges --base and --verifypackages), so it is reproducible and testable locally, and the detect job just delegates to it. LLVMSharp keeps its genuine divergence of comparing the full patch version for libLLVM. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/regenerate-native.yml | 95 ++------------- scripts/build.sh | 148 ++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 85 deletions(-) diff --git a/.github/workflows/regenerate-native.yml b/.github/workflows/regenerate-native.yml index 2b489d0e..d71043c9 100644 --- a/.github/workflows/regenerate-native.yml +++ b/.github/workflows/regenerate-native.yml @@ -43,7 +43,6 @@ 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: @@ -54,96 +53,22 @@ jobs: 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 + # Manual runs pick the families explicitly. + echo "libllvm=${{ inputs.libllvm }}" >> "$GITHUB_OUTPUT" + echo "libllvmsharp=${{ inputs.libllvmsharp }}" >> "$GITHUB_OUTPUT" 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 + # Pushes diff against the prior commit; the script owns the change-detection + # logic so it stays reproducible and testable locally. + ./scripts/build.sh --detectchanges --base "${{ github.event.before }}" | tee -a "$GITHUB_OUTPUT" 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). + # versions before regenerating. The check lives in the build script so it is + # reproducible locally (./scripts/build.sh --verifypackages). + if: steps.detect.outputs.libllvm == 'true' || steps.detect.outputs.libllvmsharp == 'true' shell: bash - run: | - set -euo pipefail - version="${{ steps.detect.outputs.llvm-version }}" - rc=0 - - nuspec_version() { sed -n 's:.*\([^<]*\).*:\1:p' "$1" | head -n1; } - nuspec_branch() { sed -n 's:.*]*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.' (expected '${version}.')" ;; - 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}.'" ;; - 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}" + run: ./scripts/build.sh --verifypackages # -------- libLLVM: lift the shared libLLVM from the most-official prebuilt source -------- diff --git a/scripts/build.sh b/scripts/build.sh index a3170a0d..b82d4995 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -9,9 +9,11 @@ done ScriptRoot="$( cd -P "$( dirname "$SOURCE" )" && pwd )" architecture='' +base='' build=false ci=false configuration='Debug' +detectchanges=false help=false llvm='' pack=false @@ -22,6 +24,7 @@ solution='' target='' test=false verbosity='minimal' +verifypackages=false properties='' while [[ $# -gt 0 ]]; do @@ -31,6 +34,10 @@ while [[ $# -gt 0 ]]; do architecture=$2 shift 2 ;; + --base) + base=$2 + shift 2 + ;; --build) build=true shift 1 @@ -43,6 +50,10 @@ while [[ $# -gt 0 ]]; do configuration=$2 shift 2 ;; + --detectchanges) + detectchanges=true + shift 1 + ;; --help) help=true shift 1 @@ -83,6 +94,10 @@ while [[ $# -gt 0 ]]; do verbosity=$2 shift 2 ;; + --verifypackages) + verifypackages=true + shift 1 + ;; *) properties="$properties $1" shift 1 @@ -127,6 +142,8 @@ function Help { echo " --pack Package build artifacts" echo " --regeneratenative Stage a native binary from the matching LLVM release" echo " (use with --target and --rid)" + echo " --verifypackages Verify the libLLVM/libLLVMSharp package versions match CMakeLists.txt" + echo " --detectchanges Print which native packages need regenerating since --base " echo "" echo "Advanced settings:" echo " --solution Path to solution to build" @@ -205,6 +222,121 @@ function GetLlvmVersion { LASTEXITCODE=0 } +function VerifyPackages { + GetLlvmVersion + + if [ "$LASTEXITCODE" != 0 ]; then + return "$LASTEXITCODE" + fi + + rc=0 + + # libLLVM packages track the LLVM version exactly, including the repository branch. + for f in "$RepoRoot"/packages/libLLVM/libLLVM/libLLVM.nuspec "$RepoRoot"/packages/libLLVM/libLLVM.runtime.*/*.nuspec; do + v="$(sed -n 's:.*\([^<]*\).*:\1:p' "$f" | head -n1)" + + if [ "$v" != "$llvm" ]; then + echo "$f: version '$v' does not match LLVM version '$llvm'" + rc=1 + fi + + b="$(sed -n 's:.*]*branch="\([^"]*\)".*:\1:p' "$f" | head -n1)" + + if [ -n "$b" ] && [ "$b" != "llvmorg-$llvm" ]; then + echo "$f: repository branch '$b' does not match 'llvmorg-$llvm'" + rc=1 + fi + done + + for v in $(grep -oE '"[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?"' "$RepoRoot/packages/libLLVM/libLLVM/runtime.json" | tr -d '"'); do + if [ "$v" != "$llvm" ]; then + echo "packages/libLLVM/libLLVM/runtime.json: mapped version '$v' does not match LLVM version '$llvm'" + rc=1 + fi + done + + # libLLVMSharp packages are the LLVM version plus an independent build revision. + for f in "$RepoRoot"/packages/libLLVMSharp/libLLVMSharp/libLLVMSharp.nuspec "$RepoRoot"/packages/libLLVMSharp/libLLVMSharp.runtime.*/*.nuspec; do + v="$(sed -n 's:.*\([^<]*\).*:\1:p' "$f" | head -n1)" + + case "$v" in + "$llvm".*) : ;; + *) + echo "$f: version '$v' is not 'llvm-version.' (expected '$llvm.')" + rc=1 + ;; + esac + done + + for v in $(grep -oE '"[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?"' "$RepoRoot/packages/libLLVMSharp/libLLVMSharp/runtime.json" | tr -d '"'); do + case "$v" in + "$llvm".*) : ;; + *) + echo "packages/libLLVMSharp/libLLVMSharp/runtime.json: mapped version '$v' is not '$llvm.'" + rc=1 + ;; + esac + done + + # The managed libLLVM pin consumes the native package. Native may be updated ahead of managed, + # but managed must never lead native (managed <= native). + managedLibLLVM="$(sed -n 's:.*Include="libLLVM"[[:space:]]\{1,\}Version="\([0-9.]*\)".*:\1:p' "$RepoRoot/Directory.Packages.props" | head -n1)" + + if [ -n "$managedLibLLVM" ] && [ "$(printf '%s\n%s\n' "$managedLibLLVM" "$llvm" | sort -V | head -n1)" != "$managedLibLLVM" ]; then + echo "Directory.Packages.props: managed libLLVM pin '$managedLibLLVM' leads the native libLLVM version '$llvm' (managed must be <= native)" + rc=1 + fi + + if [ "$rc" != 0 ]; then + echo "Update the package versions to match the tracked LLVM version ($llvm) before regenerating." + LASTEXITCODE=1 + return "$LASTEXITCODE" + fi + + echo "Package versions verified against LLVM $llvm" + LASTEXITCODE=0 +} + +function DetectChanges { + if [[ -z "$base" ]]; then + echo "--base is required with --detectchanges" + LASTEXITCODE=1 + return "$LASTEXITCODE" + fi + + GetLlvmVersion + + if [ "$LASTEXITCODE" != 0 ]; then + return "$LASTEXITCODE" + fi + + detectLibllvm=false + detectLibllvmsharp=false + + if ! git -C "$RepoRoot" cat-file -e "$base^{commit}" 2>/dev/null; then + # No resolvable baseline to diff against, so regenerate everything conservatively. + detectLibllvm=true + detectLibllvmsharp=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. + prevVersion="$(git -C "$RepoRoot" show "$base:CMakeLists.txt" 2>/dev/null | sed -n 's/^project(LLVMSharp VERSION \([0-9.]*\)).*/\1/p')" + + if [ "$llvm" != "$prevVersion" ]; then + detectLibllvm=true + fi + + # libLLVMSharp regenerates for that same reason or when its sources change. + if [ "$detectLibllvm" = true ] || ! git -C "$RepoRoot" diff --quiet "$base" HEAD -- sources/libLLVMSharp/; then + detectLibllvmsharp=true + fi + fi + + echo "libllvm=$detectLibllvm" + echo "libllvmsharp=$detectLibllvmsharp" + LASTEXITCODE=0 +} + function DownloadLlvm { runtime="$1" destination="$2" @@ -512,3 +644,19 @@ if $regeneratenative; then return "$LASTEXITCODE" fi fi + +if $verifypackages; then + VerifyPackages + + if [ "$LASTEXITCODE" != 0 ]; then + exit "$LASTEXITCODE" + fi +fi + +if $detectchanges; then + DetectChanges + + if [ "$LASTEXITCODE" != 0 ]; then + exit "$LASTEXITCODE" + fi +fi