Skip to content

Commit 0d0ac42

Browse files
ci(nuget): split symbols and strip native binaries (#596)
1 parent 9306868 commit 0d0ac42

4 files changed

Lines changed: 465 additions & 2 deletions

File tree

.github/workflows/build-native.yml

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,136 @@ jobs:
205205
New-Item -ItemType Directory -Path $OutputPath | Out-Null
206206
Copy-Item $OutputLibrary $(Join-Path $OutputPath $RenamedLibraryName)
207207
208+
# Export variables for symbol collection step
209+
echo "RUST_TARGET=$RustTarget" >> $Env:GITHUB_ENV
210+
echo "TARGET_FOLDER=$TargetFolder" >> $Env:GITHUB_ENV
211+
echo "LIB_PREFIX=$LibPrefix" >> $Env:GITHUB_ENV
212+
echo "DOTNET_OS=$DotNetOs" >> $Env:GITHUB_ENV
213+
echo "DOTNET_RID=$DotNetRid" >> $Env:GITHUB_ENV
214+
215+
- name: Collect debug symbols
216+
if: matrix.build == 'release'
217+
shell: pwsh
218+
run: |
219+
Set-PSDebug -Trace 1
220+
221+
$DotNetOs = $Env:DOTNET_OS
222+
$DotNetRid = $Env:DOTNET_RID
223+
$RustTarget = $Env:RUST_TARGET
224+
$TargetFolder = $Env:TARGET_FOLDER
225+
$LibPrefix = $Env:LIB_PREFIX
226+
227+
$SymbolsPath = Join-Path "symbols" $DotNetRid
228+
New-Item -ItemType Directory -Path $SymbolsPath -Force | Out-Null
229+
230+
$TargetDir = Join-Path "target" $RustTarget $TargetFolder
231+
232+
if ($DotNetOs -eq 'win') {
233+
# Windows: Copy PDB file
234+
$PdbFile = Join-Path $TargetDir "sspi.pdb"
235+
if (Test-Path $PdbFile) {
236+
Copy-Item $PdbFile (Join-Path $SymbolsPath "DevolutionsSspi.pdb")
237+
Write-Host "Copied PDB: $PdbFile"
238+
} else {
239+
Write-Warning "PDB file not found: $PdbFile"
240+
Get-ChildItem $TargetDir -Filter "*.pdb" | ForEach-Object { Write-Host "Found PDB: $($_.FullName)" }
241+
}
242+
} elseif ($DotNetOs -eq 'osx' -or $DotNetOs -eq 'ios') {
243+
# macOS/iOS: Generate dSYM bundle using dsymutil
244+
$DylibFile = Join-Path $TargetDir "${LibPrefix}sspi.dylib"
245+
$DsymOutput = Join-Path $SymbolsPath "libDevolutionsSspi.dylib.dSYM"
246+
if (Test-Path $DylibFile) {
247+
& dsymutil $DylibFile -o $DsymOutput
248+
Write-Host "Generated dSYM: $DsymOutput"
249+
} else {
250+
Write-Warning "Dylib file not found: $DylibFile"
251+
}
252+
} elseif ($DotNetOs -eq 'linux' -or $DotNetOs -eq 'android') {
253+
# Linux/Android: Extract debug info into a separate file
254+
$DwpFile = Join-Path $TargetDir "${LibPrefix}sspi.dwp"
255+
$SoFile = Join-Path $TargetDir "${LibPrefix}sspi.so"
256+
$DebugOutput = Join-Path $SymbolsPath "libDevolutionsSspi.so.debug"
257+
if (Test-Path $DwpFile) {
258+
# split-debuginfo=packed may produce a .dwp file
259+
Copy-Item $DwpFile (Join-Path $SymbolsPath "libDevolutionsSspi.so.dwp")
260+
Write-Host "Copied DWP: $DwpFile"
261+
} elseif (Test-Path $SoFile) {
262+
# Extract debug sections from the binary using objcopy
263+
if ($DotNetOs -eq 'android') {
264+
$ObjCopy = Join-Path $Env:ANDROID_NDK_HOME "toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objcopy"
265+
if (-not (Test-Path $ObjCopy)) {
266+
throw "llvm-objcopy not found at $ObjCopy"
267+
}
268+
} else {
269+
$ObjCopy = 'llvm-objcopy'
270+
$ObjCopyCmd = Get-Command $ObjCopy -ErrorAction SilentlyContinue
271+
if (-not $ObjCopyCmd) {
272+
throw "llvm-objcopy not found on PATH"
273+
}
274+
}
275+
Write-Host "Extracting debug info with: $ObjCopy --only-keep-debug"
276+
& $ObjCopy --only-keep-debug $SoFile $DebugOutput 2>&1 | Write-Host
277+
Write-Host "Extracted debug info: $DebugOutput"
278+
$SizeMB = [math]::Round((Get-Item $DebugOutput).Length / 1MB, 2)
279+
Write-Host "Debug file size: $SizeMB MB"
280+
} else {
281+
Write-Warning "No debug symbols found for Linux/Android"
282+
}
283+
}
284+
285+
# List collected symbols
286+
Write-Host "Collected symbols:"
287+
Get-ChildItem -Recurse $SymbolsPath | ForEach-Object { Write-Host $_.FullName }
288+
289+
- name: Strip release binaries
290+
if: matrix.build == 'release'
291+
shell: pwsh
292+
run: |
293+
$DotNetOs = $Env:DOTNET_OS
294+
$DotNetRid = $Env:DOTNET_RID
295+
$LibPrefix = $Env:LIB_PREFIX
296+
$RuntimePath = Join-Path "dependencies" "runtimes" "$DotNetRid" "native"
297+
298+
if ($DotNetOs -eq 'win') {
299+
# Windows: DLL is already separate from PDB, no stripping needed
300+
Write-Host "Windows binaries are already compact (PDB is separate)"
301+
} elseif ($DotNetOs -eq 'osx' -or $DotNetOs -eq 'ios') {
302+
$DylibFile = Join-Path $RuntimePath "${LibPrefix}DevolutionsSspi.dylib"
303+
if (Test-Path $DylibFile) {
304+
& strip -Sx $DylibFile
305+
Write-Host "Stripped dylib: $DylibFile"
306+
$SizeMB = [math]::Round((Get-Item $DylibFile).Length / 1MB, 2)
307+
Write-Host "Size after stripping: $SizeMB MB"
308+
}
309+
} elseif ($DotNetOs -eq 'android' -or $DotNetOs -eq 'linux') {
310+
$SoFile = Join-Path $RuntimePath "${LibPrefix}DevolutionsSspi.so"
311+
if (Test-Path $SoFile) {
312+
if ($DotNetOs -eq 'android') {
313+
$StripTool = Join-Path $Env:ANDROID_NDK_HOME "toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip"
314+
} else {
315+
$StripTool = 'llvm-strip'
316+
}
317+
Write-Host "Using strip tool: $StripTool"
318+
& $StripTool --strip-unneeded $SoFile 2>&1 | Write-Host
319+
Write-Host "Stripped .so ($DotNetOs): $SoFile"
320+
$SizeMB = [math]::Round((Get-Item $SoFile).Length / 1MB, 2)
321+
Write-Host "Size after stripping: $SizeMB MB"
322+
}
323+
}
324+
208325
- name: Upload native components
209326
uses: actions/upload-artifact@v4
210327
with:
211328
name: sspi-${{matrix.os}}-${{matrix.arch}}-${{matrix.build}}
212329
path: dependencies/runtimes/${{matrix.os}}-${{matrix.arch}}
213330

331+
- name: Upload debug symbols
332+
if: matrix.build == 'release'
333+
uses: actions/upload-artifact@v4
334+
with:
335+
name: sspi-${{matrix.os}}-${{matrix.arch}}-${{matrix.build}}-symbols
336+
path: symbols/${{matrix.os}}-${{matrix.arch}}
337+
214338
build-universal:
215339
name: Universal Build
216340
runs-on: ubuntu-24.04
@@ -228,6 +352,29 @@ jobs:
228352
- name: Setup CCTools
229353
uses: Devolutions/actions-public/setup-cctools@v1
230354

355+
- name: Install Apple strip (cctools)
356+
if: matrix.build == 'release'
357+
shell: pwsh
358+
run: |
359+
$DownloadVersion = "v2025.2.0"
360+
$HostPlatform = "ubuntu-$(lsb_release -rs)"
361+
$HostArch = @{'X64'='x86_64';'ARM64'='aarch64'}['${{ runner.arch }}']
362+
$PackageBaseName = "cctools-$HostArch-$HostPlatform"
363+
$DownloadFile = "${PackageBaseName}.tar.xz"
364+
$BaseDownloadUrl = "https://github.com/awakecoding/llvm-prebuilt/releases/download/$DownloadVersion"
365+
366+
curl -L -o $DownloadFile "${BaseDownloadUrl}/${DownloadFile}"
367+
tar -xf $DownloadFile -C /tmp
368+
sudo mv "/tmp/${PackageBaseName}/bin/strip" /usr/bin/apple-strip
369+
sudo chmod +x /usr/bin/apple-strip
370+
Remove-Item $DownloadFile -Force | Out-Null
371+
372+
- name: Setup LLVM
373+
uses: Devolutions/actions-public/setup-llvm@v1
374+
if: matrix.build == 'release'
375+
with:
376+
version: "18.1.8"
377+
231378
- name: Download native components
232379
uses: actions/download-artifact@v4
233380
with:
@@ -290,8 +437,64 @@ jobs:
290437
# It's perfectly valid XML, but we're dealing with plists here and dyld will not be able to read the file
291438
((Get-Content -Path (Join-Path $FrameworkDir "Info.plist") -Raw) -Replace 'PropertyList-1.0.dtd"\[\]', 'PropertyList-1.0.dtd"') | Set-Content -Path (Join-Path $FrameworkDir "Info.plist")
292439
440+
# Strip the framework executable (debug symbols will be in dSYM)
441+
if ('${{ matrix.build }}' -eq 'release') {
442+
if (-not (Test-Path "/usr/bin/apple-strip")) {
443+
throw "apple-strip not found at /usr/bin/apple-strip"
444+
}
445+
& /usr/bin/apple-strip -Sx $FrameworkExecutable
446+
Write-Host "Stripped framework executable with: /usr/bin/apple-strip -Sx"
447+
$SizeMB = [math]::Round((Get-Item $FrameworkExecutable).Length / 1MB, 2)
448+
Write-Host "Size after stripping: $SizeMB MB"
449+
}
450+
451+
- name: Generate universal dSYM
452+
if: matrix.build == 'release'
453+
shell: pwsh
454+
run: |
455+
Set-PSDebug -Trace 1
456+
457+
$SymbolsPath = Join-Path "symbols" "${{ matrix.os }}-universal"
458+
New-Item -ItemType Directory -Path $SymbolsPath -Force | Out-Null
459+
460+
$DylibFile = Join-Path "dependencies" "runtimes" "${{ matrix.os }}-universal" "native" "libDevolutionsSspi.dylib"
461+
$DsymOutput = Join-Path $SymbolsPath "libDevolutionsSspi.dylib.dSYM"
462+
463+
if (Test-Path $DylibFile) {
464+
& dsymutil $DylibFile -o $DsymOutput
465+
Write-Host "Generated universal dSYM: $DsymOutput"
466+
} else {
467+
Write-Warning "Universal dylib not found: $DylibFile"
468+
}
469+
470+
# List collected symbols
471+
Write-Host "Collected symbols:"
472+
Get-ChildItem -Recurse $SymbolsPath | ForEach-Object { Write-Host $_.FullName }
473+
474+
- name: Strip universal binary
475+
if: matrix.build == 'release'
476+
shell: pwsh
477+
run: |
478+
$DylibFile = Join-Path "dependencies" "runtimes" "${{ matrix.os }}-universal" "native" "libDevolutionsSspi.dylib"
479+
if (Test-Path $DylibFile) {
480+
if (-not (Test-Path "/usr/bin/apple-strip")) {
481+
throw "apple-strip not found at /usr/bin/apple-strip"
482+
}
483+
& /usr/bin/apple-strip -Sx $DylibFile
484+
Write-Host "Stripped universal dylib with: /usr/bin/apple-strip -Sx"
485+
$SizeMB = [math]::Round((Get-Item $DylibFile).Length / 1MB, 2)
486+
Write-Host "Size after stripping: $SizeMB MB"
487+
}
488+
293489
- name: Upload native components
294490
uses: actions/upload-artifact@v4
295491
with:
296492
name: sspi-${{ matrix.os }}-universal-${{ matrix.build }}
297493
path: dependencies/runtimes/${{ matrix.os }}-universal
494+
495+
- name: Upload universal debug symbols
496+
if: matrix.build == 'release'
497+
uses: actions/upload-artifact@v4
498+
with:
499+
name: sspi-${{ matrix.os }}-universal-${{ matrix.build }}-symbols
500+
path: symbols/${{ matrix.os }}-universal

.github/workflows/nuget.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,140 @@ jobs:
156156
Invoke-Expression $PushCmd
157157
}
158158
159+
package-symbols:
160+
name: Package debug symbols
161+
runs-on: ubuntu-latest
162+
needs: [ preflight, build-native ]
163+
164+
steps:
165+
- name: Download all symbol artifacts
166+
uses: actions/download-artifact@v4
167+
with:
168+
path: artifacts
169+
pattern: sspi-*-release-symbols
170+
171+
- name: Package symbols into zip
172+
shell: pwsh
173+
run: |
174+
Set-PSDebug -Trace 1
175+
176+
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
177+
$SymbolsDir = "symbols"
178+
New-Item -ItemType Directory -Path $SymbolsDir -Force | Out-Null
179+
180+
# Organize symbols by RID
181+
$SymbolArtifacts = Get-ChildItem -Directory "artifacts" -Filter "sspi-*-release-symbols"
182+
foreach ($Artifact in $SymbolArtifacts) {
183+
# Extract RID from artifact name (e.g., sspi-win-x64-release-symbols -> win-x64)
184+
$Rid = $Artifact.Name -Replace '^sspi-(.+)-release-symbols$', '$1'
185+
$DestDir = Join-Path $SymbolsDir $Rid
186+
New-Item -ItemType Directory -Path $DestDir -Force | Out-Null
187+
188+
# Copy all contents from the artifact
189+
Get-ChildItem -Path $Artifact.FullName -Recurse | ForEach-Object {
190+
if (-not $_.PSIsContainer) {
191+
$RelativePath = $_.FullName.Substring($Artifact.FullName.Length + 1)
192+
$DestPath = Join-Path $DestDir $RelativePath
193+
$DestParent = Split-Path $DestPath -Parent
194+
if (-not (Test-Path $DestParent)) {
195+
New-Item -ItemType Directory -Path $DestParent -Force | Out-Null
196+
}
197+
Copy-Item $_.FullName $DestPath
198+
}
199+
}
200+
}
201+
202+
Write-Host "Symbol directory structure:"
203+
Get-ChildItem -Recurse $SymbolsDir | ForEach-Object { Write-Host $_.FullName }
204+
205+
# Create the zip file
206+
$ZipName = "Devolutions.Sspi.$PackageVersion.symbols.zip"
207+
Compress-Archive -Path "$SymbolsDir/*" -DestinationPath $ZipName -Force
208+
Write-Host "Created symbols archive: $ZipName"
209+
210+
- name: Upload symbols zip
211+
uses: actions/upload-artifact@v4
212+
with:
213+
name: sspi-symbols-zip
214+
path: Devolutions.Sspi.*.symbols.zip
215+
216+
create-release:
217+
name: Create GitHub Release
218+
runs-on: ubuntu-latest
219+
if: needs.preflight.outputs.dry-run == 'false'
220+
needs:
221+
- preflight
222+
- build-managed
223+
- package-symbols
224+
- publish
225+
permissions:
226+
contents: write
227+
228+
steps:
229+
- name: Download NuGet package
230+
uses: actions/download-artifact@v4
231+
with:
232+
name: sspi-nupkg
233+
path: release-assets
234+
235+
- name: Download symbols zip
236+
uses: actions/download-artifact@v4
237+
with:
238+
name: sspi-symbols-zip
239+
path: release-assets
240+
241+
- name: Create GitHub Release
242+
env:
243+
GH_TOKEN: ${{ github.token }}
244+
GH_REPO: ${{ github.repository }}
245+
shell: pwsh
246+
run: |
247+
Set-PSDebug -Trace 1
248+
249+
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
250+
$TagName = "v$PackageVersion"
251+
$ReleaseName = "Devolutions.Sspi $PackageVersion"
252+
253+
# Get the list of assets to upload
254+
$Assets = Get-ChildItem -Path "release-assets" -File
255+
Write-Host "Release assets:"
256+
$Assets | ForEach-Object { Write-Host " - $($_.Name)" }
257+
258+
# Build the release notes
259+
$ReleaseNotes = @"
260+
## Devolutions.Sspi $PackageVersion
261+
262+
### NuGet Package
263+
- [Devolutions.Sspi on NuGet.org](https://www.nuget.org/packages/Devolutions.Sspi/$PackageVersion)
264+
265+
### Included Platforms
266+
- Windows (x64, arm64)
267+
- macOS (x64, arm64, universal)
268+
- Linux (x64, arm64)
269+
- iOS (x64, arm64, universal)
270+
- Android (x86, x64, arm, arm64)
271+
272+
### Debug Symbols
273+
The `Devolutions.Sspi.$PackageVersion.symbols.zip` archive contains debug symbols for all platforms:
274+
- Windows: `.pdb` files
275+
- macOS/iOS: `.dSYM` bundles
276+
- Linux/Android: `.debug` or `.dwp` files
277+
"@
278+
279+
# Create the release with assets
280+
$AssetArgs = @()
281+
foreach ($Asset in $Assets) {
282+
$AssetArgs += $Asset.FullName
283+
}
284+
285+
gh release create $TagName `
286+
--title $ReleaseName `
287+
--notes $ReleaseNotes `
288+
--target ${{ github.sha }} `
289+
@AssetArgs
290+
291+
Write-Host "Created release: $ReleaseName with tag $TagName"
292+
159293
notify:
160294
name: Notify failure
161295
runs-on: ubuntu-latest
@@ -164,6 +298,7 @@ jobs:
164298
- preflight
165299
- build-native
166300
- build-managed
301+
- package-symbols
167302
env:
168303
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ARCHITECTURE }}
169304
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

0 commit comments

Comments
 (0)