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" }