Skip to content

Commit 90bbc7a

Browse files
Add modern local library build script and process (#789)
* Replace buildLocalLibraries.sh with Manage-LocalLibraries.ps1 * Use and verify local NuGet feed workflow using LOCAL_NUGET_REPO env var. Reads version from SilVersions.props, packs with symbols, copies PDBs, clears cache. Works for liblcm, libpalaso, and chorus. Updates nuget.config, build.ps1, and docs. * Document the workflow in Docs/architecture
1 parent 0a8fc9a commit 90bbc7a

6 files changed

Lines changed: 564 additions & 354 deletions

File tree

Build/Manage-LocalLibraries.ps1

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
<#
2+
.SYNOPSIS
3+
Manages local SIL library versions for debugging in FieldWorks.
4+
5+
.DESCRIPTION
6+
Two modes of operation:
7+
8+
Pack mode (one or more source paths provided):
9+
Packs local checkouts of liblcm, libpalaso, and/or chorus into the
10+
local NuGet feed using each library's own version. Detects the version
11+
from produced packages, updates SilVersions.props to match, copies
12+
PDBs, and clears stale cached packages.
13+
14+
Multiple libraries can be packed in a single call. libpalaso is always
15+
packed first (other libraries may depend on it).
16+
17+
SetVersion mode (-Library and -Version, no source paths):
18+
Sets the version for a single library in SilVersions.props and clears
19+
stale cached packages. Use this to revert to an upstream version or
20+
switch to a specific version without packing.
21+
22+
To revert all libraries: git checkout Build/SilVersions.props
23+
24+
See Docs/architecture/local-library-debugging.md for the full workflow.
25+
26+
.PARAMETER Palaso
27+
Switch: include libpalaso in the pack operation.
28+
29+
.PARAMETER PalasoPath
30+
Path to a local libpalaso checkout. Overrides LIBPALASO_PATH env var.
31+
Only used when -Palaso is specified.
32+
33+
.PARAMETER Lcm
34+
Switch: include liblcm in the pack operation.
35+
36+
.PARAMETER LcmPath
37+
Path to a local liblcm checkout. Overrides LIBLCM_PATH env var.
38+
Only used when -Lcm is specified.
39+
40+
.PARAMETER Chorus
41+
Switch: include chorus in the pack operation.
42+
43+
.PARAMETER ChorusPath
44+
Path to a local chorus checkout. Overrides LIBCHORUS_PATH env var.
45+
Only used when -Chorus is specified.
46+
47+
.PARAMETER Library
48+
Which library to set a version for (SetVersion mode only):
49+
liblcm, libpalaso, or chorus.
50+
51+
.PARAMETER Version
52+
Sets the version in SilVersions.props (SetVersion mode). Use to revert
53+
to an upstream version. Not used in pack mode.
54+
55+
.EXAMPLE
56+
.\Build\Manage-LocalLibraries.ps1 -Palaso -PalasoPath C:\Repos\libpalaso
57+
Packs libpalaso, detects its version, and updates SilVersions.props.
58+
59+
.EXAMPLE
60+
.\Build\Manage-LocalLibraries.ps1 -Palaso -Chorus -ChorusPath C:\Repos\chorus
61+
Packs libpalaso (from env var) and chorus first, then chorus.
62+
63+
.EXAMPLE
64+
.\Build\Manage-LocalLibraries.ps1 -Library libpalaso -Version 17.0.0
65+
Sets libpalaso version to 17.0.0 in SilVersions.props (e.g. to revert).
66+
#>
67+
[CmdletBinding()]
68+
param(
69+
[switch]$Palaso,
70+
[string]$PalasoPath,
71+
72+
[switch]$Lcm,
73+
[string]$LcmPath,
74+
75+
[switch]$Chorus,
76+
[string]$ChorusPath,
77+
78+
[ValidateSet('liblcm', 'libpalaso', 'chorus')]
79+
[string]$Library,
80+
81+
[string]$Version
82+
)
83+
84+
$ErrorActionPreference = "Stop"
85+
86+
# ---------------------------------------------------------------------------
87+
# Library-specific configuration
88+
# ---------------------------------------------------------------------------
89+
90+
$LibraryConfig = @{
91+
libpalaso = @{
92+
VersionProperty = 'SilLibPalasoVersion'
93+
PdbRelativeDir = 'output/Debug/net462'
94+
CachePrefixes = @(
95+
'sil.core', 'sil.windows', 'sil.dblbundle', 'sil.writingsystems',
96+
'sil.dictionary', 'sil.lift', 'sil.lexicon', 'sil.archiving',
97+
'sil.media', 'sil.scripture', 'sil.testutilities'
98+
)
99+
EnvVar = 'LIBPALASO_PATH'
100+
}
101+
liblcm = @{
102+
VersionProperty = 'SilLcmVersion'
103+
PdbRelativeDir = 'artifacts/Debug/net462'
104+
CachePrefixes = @('sil.lcmodel')
105+
EnvVar = 'LIBLCM_PATH'
106+
}
107+
chorus = @{
108+
VersionProperty = 'SilChorusVersion'
109+
PdbRelativeDir = 'output/Debug/net462'
110+
CachePrefixes = @('sil.chorus')
111+
EnvVar = 'LIBCHORUS_PATH'
112+
}
113+
}
114+
115+
# Pack order: libpalaso first (other libraries may depend on it)
116+
$PackOrder = @('libpalaso', 'liblcm', 'chorus')
117+
118+
# ---------------------------------------------------------------------------
119+
# Read SilVersions.props
120+
# ---------------------------------------------------------------------------
121+
122+
$repoRoot = Split-Path $PSScriptRoot -Parent
123+
$versionPropsPath = Join-Path $PSScriptRoot "SilVersions.props"
124+
if (-not (Test-Path $versionPropsPath)) {
125+
throw "SilVersions.props not found at $versionPropsPath"
126+
}
127+
128+
[xml]$versionProps = Get-Content -LiteralPath $versionPropsPath
129+
130+
# ---------------------------------------------------------------------------
131+
# Helper: get version node for a library
132+
# ---------------------------------------------------------------------------
133+
134+
function Get-VersionNode {
135+
param([string]$LibName)
136+
$cfg = $LibraryConfig[$LibName]
137+
$node = $versionProps.SelectSingleNode(
138+
"//PropertyGroup[@Label='SIL Ecosystem Versions']/$($cfg.VersionProperty)")
139+
if (-not $node) {
140+
throw "Could not find <$($cfg.VersionProperty)> in SilVersions.props"
141+
}
142+
return $node
143+
}
144+
145+
# ---------------------------------------------------------------------------
146+
# Helper: update SilVersions.props and clear stale cached packages
147+
# ---------------------------------------------------------------------------
148+
149+
function Update-VersionAndClearCache {
150+
param([string]$LibName, [string]$NewVersion)
151+
$cfg = $LibraryConfig[$LibName]
152+
$node = Get-VersionNode $LibName
153+
$node.InnerText = $NewVersion
154+
$versionProps.Save($versionPropsPath)
155+
Write-Host "Updated SilVersions.props ($($cfg.VersionProperty) = $NewVersion)" -ForegroundColor Yellow
156+
157+
$packagesDir = Join-Path $repoRoot "packages"
158+
if (Test-Path $packagesDir) {
159+
$patterns = $cfg.CachePrefixes | ForEach-Object { "$packagesDir/$_*" }
160+
$stale = @(Get-ChildItem -Path $patterns -Directory -ErrorAction SilentlyContinue)
161+
if ($stale.Count -gt 0) {
162+
$stale | Remove-Item -Recurse -Force
163+
Write-Host "Cleared $($stale.Count) stale package folder(s) from packages/." -ForegroundColor Yellow
164+
}
165+
}
166+
}
167+
168+
# ---------------------------------------------------------------------------
169+
# Helper: extract version from a .nupkg filename
170+
# ---------------------------------------------------------------------------
171+
# Split on '.', find the first segment starting with a digit — everything
172+
# from there onward (minus .nupkg) is the version.
173+
# E.g. SIL.Windows.Forms.Keyboarding.18.0.0-beta.nupkg → 18.0.0-beta
174+
175+
function Get-PackageVersion {
176+
param([string]$FileName)
177+
$base = $FileName -replace '\.nupkg$', ''
178+
$segments = $base -split '\.'
179+
for ($i = 0; $i -lt $segments.Count; $i++) {
180+
if ($segments[$i] -match '^\d') {
181+
return ($segments[$i..($segments.Count - 1)] -join '.')
182+
}
183+
}
184+
return $null
185+
}
186+
187+
# ---------------------------------------------------------------------------
188+
# Helper: pack a single library
189+
# ---------------------------------------------------------------------------
190+
191+
function Invoke-PackLibrary {
192+
param([string]$LibName, [string]$SourceDir, [string]$LocalRepo)
193+
194+
$cfg = $LibraryConfig[$LibName]
195+
$node = Get-VersionNode $LibName
196+
197+
Write-Host ""
198+
Write-Host "========================================" -ForegroundColor Cyan
199+
Write-Host "Packing $LibName" -ForegroundColor Cyan
200+
Write-Host " Source: $SourceDir" -ForegroundColor Cyan
201+
Write-Host " Current: $($node.InnerText.Trim())" -ForegroundColor Cyan
202+
Write-Host " Output: $LocalRepo" -ForegroundColor Cyan
203+
Write-Host "========================================" -ForegroundColor Cyan
204+
205+
# Record timestamp before pack so we can find newly-produced packages
206+
$packStart = Get-Date
207+
208+
Write-Host "Running dotnet pack..." -ForegroundColor Cyan
209+
$packArgs = @(
210+
'pack'
211+
$SourceDir
212+
'-c', 'Debug'
213+
"-p:IncludeSymbols=true"
214+
"-p:SymbolPackageFormat=snupkg"
215+
'--output', $LocalRepo
216+
)
217+
218+
& dotnet @packArgs
219+
if ($LASTEXITCODE -ne 0) {
220+
throw "dotnet pack failed for $LibName."
221+
}
222+
223+
# Find .nupkg files created after pack started (exclude .snupkg and test pkgs)
224+
$newPackages = @(
225+
Get-ChildItem -Path $LocalRepo -Filter "*.nupkg" -File |
226+
Where-Object { $_.LastWriteTime -ge $packStart -and $_.Extension -eq '.nupkg' -and $_.Name -notmatch 'tests' }
227+
)
228+
229+
if ($newPackages.Count -eq 0) {
230+
$currentVer = (Get-VersionNode $LibName).InnerText.Trim()
231+
Write-Host ""
232+
Write-Host "WARNING: No new .nupkg files were produced for $LibName." -ForegroundColor Yellow
233+
Write-Host " The library version may not have changed since the last pack." -ForegroundColor Yellow
234+
Write-Host " Current version in SilVersions.props: $currentVer" -ForegroundColor Yellow
235+
Write-Host " Skipping version update for $LibName." -ForegroundColor Yellow
236+
return
237+
}
238+
239+
Write-Host "New packages found:" -ForegroundColor Gray
240+
$newPackages | ForEach-Object { Write-Host " $($_.Name)" -ForegroundColor Gray }
241+
242+
$detectedVersions = @($newPackages | ForEach-Object { Get-PackageVersion $_.Name } |
243+
Where-Object { $_ } | Sort-Object -Unique)
244+
245+
Write-Host "Detected version(s): $($detectedVersions -join ', ')" -ForegroundColor Gray
246+
247+
if ($detectedVersions.Count -eq 0) {
248+
throw "Could not parse version from produced packages: $($newPackages.Name -join ', ')"
249+
}
250+
if ($detectedVersions.Count -gt 1) {
251+
Write-Host "WARNING: Multiple versions detected in produced packages:" -ForegroundColor Red
252+
$detectedVersions | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
253+
throw "Expected all packages to share one version. Clean $LocalRepo and retry."
254+
}
255+
256+
$packVersion = $detectedVersions[0]
257+
Write-Host ""
258+
Write-Host "Pack complete ($($newPackages.Count) package(s), version $packVersion)." -ForegroundColor Green
259+
260+
# Update SilVersions.props and clear cache
261+
Update-VersionAndClearCache -LibName $LibName -NewVersion $packVersion
262+
Write-Host "To revert: git checkout Build/SilVersions.props" -ForegroundColor Yellow
263+
264+
# Copy PDB files to Output/Debug/ and Downloads/
265+
$pdbSourceDir = Join-Path $SourceDir $cfg.PdbRelativeDir
266+
267+
if (Test-Path $pdbSourceDir) {
268+
$outputDebugDir = Join-Path $repoRoot "Output/Debug"
269+
$downloadsDir = Join-Path $repoRoot "Downloads"
270+
271+
foreach ($dir in @($outputDebugDir, $downloadsDir)) {
272+
if (-not (Test-Path $dir)) {
273+
New-Item -Path $dir -ItemType Directory -Force | Out-Null
274+
}
275+
}
276+
277+
$pdbFiles = @(Get-ChildItem -Path $pdbSourceDir -Filter "*.pdb" -File)
278+
if ($pdbFiles.Count -gt 0) {
279+
Write-Host "Copying $($pdbFiles.Count) PDB file(s) to Output/Debug/ and Downloads/..." -ForegroundColor Cyan
280+
$pdbFiles | Copy-Item -Destination $outputDebugDir -Force
281+
$pdbFiles | Copy-Item -Destination $downloadsDir -Force
282+
}
283+
else {
284+
Write-Host "No PDB files found in $pdbSourceDir" -ForegroundColor Yellow
285+
}
286+
}
287+
else {
288+
Write-Host "PDB source directory not found: $pdbSourceDir (PDBs will only be in .snupkg)" -ForegroundColor Yellow
289+
}
290+
291+
Write-Host ""
292+
Write-Host "[OK] $LibName packed successfully." -ForegroundColor Green
293+
}
294+
295+
# ===========================================================================
296+
# Build the list of libraries to pack (in order)
297+
# ===========================================================================
298+
299+
# Map switches and explicit paths to library names
300+
$switchMap = @{
301+
libpalaso = @{ Enabled = [bool]$Palaso; ExplicitPath = $PalasoPath }
302+
liblcm = @{ Enabled = [bool]$Lcm; ExplicitPath = $LcmPath }
303+
chorus = @{ Enabled = [bool]$Chorus; ExplicitPath = $ChorusPath }
304+
}
305+
306+
# Resolve source paths: explicit path > env var (only when switch is set)
307+
$toPack = [ordered]@{}
308+
foreach ($lib in $PackOrder) {
309+
$sw = $switchMap[$lib]
310+
if (-not $sw.Enabled) { continue }
311+
312+
$cfg = $LibraryConfig[$lib]
313+
$path = $sw.ExplicitPath
314+
if (-not $path) {
315+
$path = [System.Environment]::GetEnvironmentVariable($cfg.EnvVar)
316+
}
317+
if (-not $path) {
318+
throw "-$($lib) was specified but no path was provided and $($cfg.EnvVar) is not set."
319+
}
320+
if (-not (Test-Path $path)) {
321+
throw "Source path for $lib does not exist: $path"
322+
}
323+
$toPack[$lib] = $path
324+
}
325+
326+
# ===========================================================================
327+
# Determine mode
328+
# ===========================================================================
329+
330+
if ($toPack.Count -gt 0) {
331+
# -----------------------------------------------------------------------
332+
# Pack mode
333+
# -----------------------------------------------------------------------
334+
if ($Version) {
335+
Write-Host "WARNING: -Version is ignored in pack mode (version is detected from produced packages)." -ForegroundColor Yellow
336+
}
337+
338+
$localRepo = $env:LOCAL_NUGET_REPO
339+
if (-not $localRepo) {
340+
throw "The LOCAL_NUGET_REPO environment variable is not set. Set it to a folder path (e.g. C:\localnugetpackages)."
341+
}
342+
if (-not (Test-Path $localRepo)) {
343+
Write-Host "Creating local NuGet repo folder: $localRepo" -ForegroundColor Yellow
344+
New-Item -Path $localRepo -ItemType Directory -Force | Out-Null
345+
}
346+
347+
# Ensure local NuGet source is registered (user-level config)
348+
$sourceList = & dotnet nuget list source 2>&1
349+
$normalizedRepo = [System.IO.Path]::GetFullPath($localRepo).TrimEnd('\', '/')
350+
$alreadyRegistered = $sourceList | Where-Object {
351+
$_.Trim() -replace '[\\/]$', '' -ieq $normalizedRepo
352+
}
353+
if (-not $alreadyRegistered) {
354+
& dotnet nuget add source $localRepo --name local 2>&1 | Out-Null
355+
Write-Host "Added local NuGet source: $localRepo" -ForegroundColor Yellow
356+
}
357+
358+
Write-Host ""
359+
Write-Host "Libraries to pack: $($toPack.Keys -join ', ')" -ForegroundColor Cyan
360+
361+
foreach ($lib in $toPack.Keys) {
362+
Invoke-PackLibrary -LibName $lib -SourceDir $toPack[$lib] -LocalRepo $localRepo
363+
}
364+
365+
Write-Host ""
366+
Write-Host "========================================" -ForegroundColor Green
367+
Write-Host "[OK] All libraries packed. Run .\build.ps1 to build." -ForegroundColor Green
368+
Write-Host "========================================" -ForegroundColor Green
369+
}
370+
elseif ($Library -and $Version) {
371+
# -----------------------------------------------------------------------
372+
# SetVersion mode
373+
# -----------------------------------------------------------------------
374+
$node = Get-VersionNode $Library
375+
376+
Write-Host ""
377+
Write-Host "Manage-LocalLibrary (SetVersion)" -ForegroundColor Cyan
378+
Write-Host " Library: $Library" -ForegroundColor Cyan
379+
Write-Host " Current: $($node.InnerText.Trim())" -ForegroundColor Cyan
380+
Write-Host " New: $Version" -ForegroundColor Cyan
381+
Write-Host ""
382+
383+
Update-VersionAndClearCache -LibName $Library -NewVersion $Version
384+
385+
Write-Host ""
386+
Write-Host "[OK] $Library version set to $Version" -ForegroundColor Green
387+
Write-Host "Run .\build.ps1 to restore and build with the new version." -ForegroundColor Cyan
388+
}
389+
else {
390+
throw "Nothing to do. Use -Palaso/-Lcm/-Chorus switches to pack, or -Library and -Version to set a version.`nExamples:`n .\Build\Manage-LocalLibraries.ps1 -Palaso -PalasoPath C:\Repos\libpalaso`n .\Build\Manage-LocalLibraries.ps1 -Palaso -Chorus`n .\Build\Manage-LocalLibraries.ps1 -Library libpalaso -Version 17.0.0"
391+
}

0 commit comments

Comments
 (0)