From e07bfe7f1dab65288b9d8066903dcd9871520be1 Mon Sep 17 00:00:00 2001 From: GabrielDuf Date: Fri, 28 Aug 2026 14:52:37 -0400 Subject: [PATCH] Build Linux releases against a glibc 2.27 baseline NativeAOT binaries inherit the glibc symbol versions of the machine that links them. Both Linux legs built on ubuntu-latest (glibc 2.39), so RyuJIT's direct fmod/fmodf references (dotnet/runtime@503970c) bound to GLIBC_2.38 and the app died at startup with "libm.so.6: version `GLIBC_2.38' not found" on Ubuntu 22.04, Debian 12, RHEL 8/9, Amazon Linux 2023 and Fedora <= 38. Those two symbols were the only references above 2.34 on either architecture. Publish the Linux legs inside .NET's official cross-build image, whose /crossrootfs is built from Ubuntu 18.04, so every reference resolves against glibc 2.27. This also replaces the hand-rolled ubuntu-ports arm64 toolchain setup, since the image carries the arm64 rootfs. The SDK tag is read from global.json so the container does not drift off the pinned 10.0.103 the way mcr.microsoft.com/dotnet/sdk:10.0 (now 10.0.400) would, and the Android and GHC toolchains are reclaimed to leave room for the ~6 GB image. check-linux-glibc.ps1 fails the build when any shipped binary requires more than LINUX_GLIBC_BASELINE, so a future runner-image bump cannot silently raise the floor again. package-linux.ps1 feeds the same detection into a Depends: libc6 (>= x.y) field, so apt refuses to install a package the system cannot run instead of installing one that dies on launch; rpm already derived this from its automatic ELF requires. Fixes #5250 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-release.yml | 106 +++++++++++++++++----------- README.md | 2 + scripts/check-linux-glibc.ps1 | 88 +++++++++++++++++++++++ scripts/package-linux.ps1 | 15 +++- 4 files changed, 170 insertions(+), 41 deletions(-) create mode 100644 scripts/check-linux-glibc.ps1 diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 2e68a19a8f..dbd34c9380 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -22,6 +22,9 @@ on: type: boolean default: true +env: + LINUX_GLIBC_BASELINE: '2.27' + jobs: preflight: name: Preflight @@ -371,10 +374,14 @@ jobs: name: linux-x64 runtime: linux-x64 publish_profile: linux-x64-NativeAot + cross_image_arch: amd64 + cross_sysroot: /crossrootfs/x64 - os: ubuntu-latest name: linux-arm64 runtime: linux-arm64 publish_profile: linux-arm64-NativeAot + cross_image_arch: arm64 + cross_sysroot: /crossrootfs/arm64 steps: - name: Checkout @@ -393,52 +400,31 @@ jobs: restore-keys: | ${{ runner.os }}-nuget- - - name: Install Linux arm64 NativeAOT toolchain - if: matrix.runtime == 'linux-arm64' + - name: Free disk space for cross-build images + if: runner.os == 'Linux' shell: bash run: | - set -euo pipefail - - . /etc/os-release - sudo dpkg --add-architecture arm64 + sudo rm -rf /usr/local/lib/android /opt/ghc || true + df -h / - # Keep the runner's default Ubuntu feeds bound to amd64, then add the - # Ubuntu Ports arm64 feed that provides the target CRT and zlib objects. - if compgen -G "/etc/apt/sources.list.d/*.sources" > /dev/null; then - for source in /etc/apt/sources.list.d/*.sources; do - if ! grep -q '^Architectures:' "$source"; then - sudo sed -i '/^Types: deb$/a Architectures: amd64' "$source" - fi - done - fi + - name: Build Linux cross-compilation image + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail - if [ -f /etc/apt/sources.list ]; then - sudo sed -i -E '/^deb /s/^deb /deb [arch=amd64] /' /etc/apt/sources.list - fi + SDK_TAG="$(jq -er '.sdk.version' global.json)" - sudo tee /etc/apt/sources.list.d/ubuntu-arm64.sources >/dev/null <[A-Za-z_][A-Za-z0-9_]*)@+GLIBC_(?\d+(?:\.\d+)+)')) { + $Version = [version] $Match.Groups['version'].Value + + if ($null -eq $FileHighest -or $Version -gt $FileHighest) { $FileHighest = $Version } + if ($null -eq $HighestVersion -or $Version -gt $HighestVersion) { $HighestVersion = $Version } + + if ($Ceiling -and $Version -gt $Ceiling) { + $Offenders += [pscustomobject]@{ + File = $File.Name + Symbol = $Match.Groups['symbol'].Value + Version = $Version + } + } + } + + $Reported = if ($FileHighest) { "GLIBC_$FileHighest" } else { 'no versioned glibc symbols' } + Write-Host ("{0}: requires {1}" -f $File.Name, $Reported) +} + +if ($null -eq $HighestVersion) { + throw "No versioned glibc symbols were found under '$ResolvedPath'" +} + +Write-Host ("Highest glibc requirement: GLIBC_$HighestVersion") + +if ($Offenders.Count -gt 0) { + Write-Host "References above the GLIBC_$Ceiling baseline:" + $Offenders | + Sort-Object -Property File, Symbol -Unique | + ForEach-Object { Write-Host " $($_.File): $($_.Symbol)@GLIBC_$($_.Version)" } + + throw "Binaries require GLIBC_$HighestVersion but the supported baseline is GLIBC_$Ceiling; they will not start on older distributions." +} + +Write-Output $HighestVersion.ToString() diff --git a/scripts/package-linux.ps1 b/scripts/package-linux.ps1 index 26b3ca09bb..0245d37da8 100644 --- a/scripts/package-linux.ps1 +++ b/scripts/package-linux.ps1 @@ -53,6 +53,10 @@ .PARAMETER IconSourcePath Source path for the installed desktop icon. + +.PARAMETER MinGlibcVersion + Minimum glibc version recorded in the .deb Depends field (e.g. "2.27"). + When omitted, it is detected from the binaries in SourceDir. #> [CmdletBinding()] @@ -81,7 +85,8 @@ param( [string] $Url = 'https://github.com/Devolutions/UniGetUI', [string] $AppExecutableName = 'UniGetUI', [string] $LauncherName = 'unigetui', - [string] $IconSourcePath = (Join-Path $PSScriptRoot '..' 'src' 'SharedAssets' 'Assets' 'Images' 'icon.png') + [string] $IconSourcePath = (Join-Path $PSScriptRoot '..' 'src' 'SharedAssets' 'Assets' 'Images' 'icon.png'), + [string] $MinGlibcVersion = '' ) $ErrorActionPreference = 'Stop' @@ -179,6 +184,13 @@ try { $ControlDir = Join-Path $TmpDir 'control' New-Item -ItemType Directory -Path $ControlDir | Out-Null + $GlibcVersion = $MinGlibcVersion + if (-not $GlibcVersion) { + $GlibcVersion = & (Join-Path $PSScriptRoot 'check-linux-glibc.ps1') -Path $SourceDir | + Select-Object -Last 1 + } + if (-not $GlibcVersion) { throw "Unable to determine the glibc requirement for '$SourceDir'" } + # dpkg requires LF line endings and a trailing newline in control files $ControlLines = @( "Package: $PackageName", @@ -186,6 +198,7 @@ try { "Architecture: $Architecture", "Maintainer: $Maintainer", "Installed-Size: $InstalledSizeKb", + "Depends: libc6 (>= $GlibcVersion)", "Homepage: $Url", "Description: $Description", "Priority: optional",