Files
MoShiro/ci/Bundle-QuicRuntime.ps1
T
justme 29d998bc94
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline failed
ci/woodpecker/push/dev-image unknown status
build(quic): bundle Linux runtime libraries [CI]
2026-10-10 04:01:24 +08:00

142 lines
7.3 KiB
PowerShell

param(
[Parameter(Mandatory)]
[ValidateSet('linux-x64', 'linux-arm64', 'linux-musl-x64', 'linux-musl-arm64')]
[string]$RuntimeIdentifier,
[Parameter(Mandatory)]
[string]$Destination
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$arch = if ($RuntimeIdentifier.EndsWith('arm64')) { 'arm64' } else { 'amd64' }
$root = Split-Path -Parent $PSScriptRoot
$cacheRoot = if ($env:MOSHIRO_QUIC_CACHE) {
$env:MOSHIRO_QUIC_CACHE
}
elseif ($env:NUGET_PACKAGES) {
Join-Path (Split-Path -Parent $env:NUGET_PACKAGES) 'moshiro-quic-runtime'
}
else {
Join-Path $root '.release\quic-package-cache'
}
$cache = Join-Path $cacheRoot $RuntimeIdentifier
$null = New-Item -ItemType Directory -Force -Path $cache, $Destination
Get-ChildItem -LiteralPath $Destination -Filter '*.so.*' -File -ErrorAction SilentlyContinue | Remove-Item -Force
function Invoke-Tar([string[]]$Arguments) {
$tar = if (Get-Command tar.exe -ErrorAction SilentlyContinue) { 'tar.exe' } else { 'tar' }
& $tar @Arguments
if ($LASTEXITCODE -ne 0) { throw "tar.exe failed ($LASTEXITCODE): $($Arguments -join ' ')" }
}
function Copy-LibrariesFromDataTar([string]$DataTar, [string]$ExtractRoot, [string]$ExcludePattern = '') {
$tar = if (Get-Command tar.exe -ErrorAction SilentlyContinue) { 'tar.exe' } else { 'tar' }
$entries = & $tar -tf $DataTar
if ($LASTEXITCODE -ne 0) { throw "Could not list $DataTar" }
foreach ($entry in $entries) {
$normalized = $entry -replace '^\./', ''
if ($normalized -notmatch '^(?:usr/)?lib/.+\.so\.\d+(?:\.\d+)+$') { continue }
$leaf = [IO.Path]::GetFileName($normalized)
if ($ExcludePattern -and $leaf -match $ExcludePattern) { continue }
$soname = [regex]::Match($leaf, '^(.+\.so\.\d+)')
if (-not $soname.Success) { continue }
Invoke-Tar @('-xf', $DataTar, '-C', $ExtractRoot, $entry)
$source = Join-Path $ExtractRoot ($normalized -replace '/', [IO.Path]::DirectorySeparatorChar)
Copy-Item -LiteralPath $source -Destination (Join-Path $Destination $soname.Groups[1].Value) -Force
}
}
function Copy-LibrariesFromTree([string]$ExtractRoot) {
foreach ($source in Get-ChildItem -LiteralPath $ExtractRoot -Recurse -File) {
if ($source.DirectoryName -notmatch '[\\/]usr[\\/]lib([\\/]|$)') { continue }
$soname = [regex]::Match($source.Name, '^(.+\.so\.\d+)\.\d+')
if ($soname.Success) {
Copy-Item -LiteralPath $source.FullName -Destination (Join-Path $Destination $soname.Groups[1].Value) -Force
}
}
}
function Get-DebLibrary([string]$IndexUrl, [string]$PackagePattern, [string]$Name, [string]$ExcludePattern = '') {
$index = (Invoke-WebRequest -Uri $IndexUrl -UseBasicParsing).Content
$links = [regex]::Matches($index, 'href="([^"]+\.deb)"') | ForEach-Object { $_.Groups[1].Value -replace '^\./', '' }
$file = $links | Where-Object { $_ -match $PackagePattern } | Sort-Object -Descending | Select-Object -First 1
if (-not $file) { throw "Could not find $Name ($arch) in $IndexUrl" }
$packagePath = Join-Path $cache $file
if (-not (Test-Path -LiteralPath $packagePath)) {
Invoke-WebRequest -Uri "$IndexUrl$file" -OutFile $packagePath -UseBasicParsing
}
$unpack = Join-Path $cache ([IO.Path]::GetFileNameWithoutExtension($file))
$null = New-Item -ItemType Directory -Force -Path $unpack
if (Get-Command ar -ErrorAction SilentlyContinue) {
Push-Location $unpack
try {
& ar x $packagePath
if ($LASTEXITCODE -ne 0) { throw "Could not unpack Debian package $file" }
}
finally { Pop-Location }
}
else {
Invoke-Tar @('-xf', $packagePath, '-C', $unpack)
}
$dataTar = Get-ChildItem -LiteralPath $unpack -Filter 'data.tar.*' | Select-Object -First 1
if (-not $dataTar) { throw "Debian package $file has no data archive" }
Copy-LibrariesFromDataTar $dataTar.FullName $unpack $ExcludePattern
}
function Get-ApkLibrary([string]$Repository, [string]$PackageName) {
$apkArch = if ($arch -eq 'amd64') { 'x86_64' } else { 'aarch64' }
$base = "https://dl-cdn.alpinelinux.org/alpine/v3.22/$Repository/$apkArch"
$indexPath = Join-Path $cache "$Repository-APKINDEX.tar.gz"
if (-not (Test-Path -LiteralPath $indexPath) -or (Get-Item -LiteralPath $indexPath).LastWriteTimeUtc -lt [DateTime]::UtcNow.AddHours(-24)) {
Invoke-WebRequest -Uri "$base/APKINDEX.tar.gz" -OutFile $indexPath -UseBasicParsing
}
$indexDirectory = Join-Path $cache "$Repository-index"
$null = New-Item -ItemType Directory -Force -Path $indexDirectory
Invoke-Tar @('-xzf', $indexPath, '-C', $indexDirectory)
$indexFile = Get-ChildItem -LiteralPath $indexDirectory -File | Where-Object Name -Like 'APKINDEX*' | Select-Object -First 1 -ExpandProperty FullName
if (-not $indexFile) { throw "Alpine index archive has no APKINDEX: $indexPath" }
$records = (Get-Content -LiteralPath $indexFile -Raw) -split "`n`n"
$record = $records | Where-Object { $_ -match "(?m)^P:$([regex]::Escape($PackageName))\r?$" } | Select-Object -First 1
if (-not $record) { throw "Could not find Alpine package $PackageName in $Repository/$apkArch" }
$version = [regex]::Match($record, '(?m)^V:(.+)$').Groups[1].Value.Trim()
$packageFile = "$PackageName-$version.apk"
if (-not $version -or -not $packageFile) { throw "Incomplete APKINDEX record for $PackageName" }
$apkPath = Join-Path $cache $packageFile
if (-not (Test-Path -LiteralPath $apkPath)) { Invoke-WebRequest -Uri "$base/$packageFile" -OutFile $apkPath -UseBasicParsing }
$unpack = Join-Path $cache "$PackageName-$version"
$null = New-Item -ItemType Directory -Force -Path $unpack
Invoke-Tar @('-xzf', $apkPath, '-C', $unpack)
Copy-LibrariesFromTree $unpack
}
if ($RuntimeIdentifier.StartsWith('linux-musl-')) {
Get-ApkLibrary 'community' 'libmsquic'
Get-ApkLibrary 'main' 'numactl'
Get-ApkLibrary 'main' 'lttng-ust'
Get-ApkLibrary 'main' 'zlib'
}
else {
# Keep these Debian 12 builds: selecting the newest package in the shared
# Debian pool can silently raise the minimum glibc above Bookworm's 2.36.
Get-DebLibrary 'https://deb.debian.org/debian/pool/main/n/numactl/' "^libnuma1_2\.0\.16-1_$arch\.deb$" 'libnuma'
Get-DebLibrary 'https://deb.debian.org/debian/pool/main/libz/libzstd/' "^libzstd1_1\.5\.4\+dfsg2-5_$arch\.deb$" 'libzstd'
Get-DebLibrary 'https://deb.debian.org/debian/pool/main/z/zlib/' "^zlib1g_1\.2\.13\.dfsg-1_$arch\.deb$" 'zlib'
# MsQuic's LTTng plugin is optional and is not loaded by the node runtime.
Get-DebLibrary 'https://packages.microsoft.com/debian/12/prod/pool/main/libm/libmsquic/' "^libmsquic_\d+(?:\.\d+){1,3}_$arch\.deb$" 'MsQuic' '^libmsquic\.lttng\.so\.'
}
if (-not (Test-Path -LiteralPath (Join-Path $Destination 'libmsquic.so.2'))) {
throw "Bundled runtime is missing libmsquic.so.2 for $RuntimeIdentifier"
}
if (-not (Test-Path -LiteralPath (Join-Path $Destination 'libnuma.so.1'))) {
throw "Bundled runtime is missing libnuma.so.1 for $RuntimeIdentifier"
}
if (-not (Test-Path -LiteralPath (Join-Path $Destination 'libz.so.1'))) {
throw "Bundled runtime is missing libz.so.1 for $RuntimeIdentifier"
}
Get-ChildItem -LiteralPath $Destination -Filter '*.so.*' | ForEach-Object { Write-Output "bundled $($_.Name) for $RuntimeIdentifier" }