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
]