Migration Period: November 7-21, 2025
Base Commit: 8e508dab484fafafb641298ed9071f03070f7c8b
Final Commit: 58d04c191260188832554740dfa642702c45721b
Total Commits: 115
Status: ✅ COMPLETE - All systems operational
FieldWorks migrated from legacy .NET Framework project formats to modern SDK-style projects (Nov 7–21, 2025). 119 projects converted, 140 legacy files removed, 15+ runtime bugs fixed post-migration. Zero legacy build paths remain.
If something broke after this migration, start with the Quick Blame Lookup below, then check Code Fixes and Patterns and Migration Bug Fixes.
"Did the SDK migration cause this?" — check here first.
| Symptom | Likely Cause | Fix / Ticket |
|---|---|---|
FileLoadException or wrong DLL version at runtime |
Stale DLLs in Output/ shadowing NuGet packages |
Run Remove-StaleDlls.ps1; see LT-22382 |
REGDB_E_CLASSNOTREG (COM class not registered) |
Missing reg-free COM manifest | Rebuild with RegFree.targets; check FieldWorks.exe.manifest |
| Black screen / rendering corruption | GDI+ Bitmap incompatible with GDI BitBlt after SDK switch |
Use native GDI DC instead of System.Drawing.Bitmap |
InitializeComponent() not found (CS0103) |
WPF project using wrong SDK | Switch to Microsoft.NET.Sdk.WindowsDesktop |
| Duplicate type errors (CS0436) | Test code compiled into production assembly | Add <Compile Remove="ProjectTests/**" /> |
Duplicate AssemblyInfo attributes (CS0579) |
SDK auto-generates + manual AssemblyInfo.cs |
Set GenerateAssemblyInfo=true; remove manual duplicates |
| NuGet version conflict (NU1605) | Transitive downgrade across projects | Pin version in Directory.Packages.props |
| Stack overflow on startup | Recursive init path exposed by changed load order | See LT-22384 |
| Memory leak in tree views | Event handlers not unsubscribed | See LT-22392 |
| UI button duplicated / dialog missing | Control init order changed subtly | See LT-22378, LT-22395, LT-22414 |
System.Security.Permissions conflict |
DotNetZip transitive dependency |
Global pin in Directory.Build.props; see LT-22394 |
| XPath failures with apostrophes | Unquoted string literals in XPath predicates | Use concat() quoting; see Code Fixes #10 |
- Quick Blame Lookup
- Migration Overview
- Project Conversions
- Build System Modernization
- Central Package Management (CPM)
- 64-bit and Reg-Free COM
- Test Framework Upgrades
- Code Fixes and Patterns
- Migration Bug Fixes (LT-223xx)
- Legacy Removal
- Tooling and Automation
- Statistics
- Lessons Learned
- Validation Status
- Appendix: Related Documents
The migration occurred in multiple coordinated phases:
- Automated conversion of 119 .csproj files using
convertToSDK.py - Package reference updates and conflict resolution
- Removal of obsolete files
- Initial NUnit 3 → NUnit 4 migration
- Fixed package version mismatches (NU1605 errors)
- Resolved duplicate AssemblyInfo attributes (CS0579)
- Fixed XAML code generation issues (CS0103)
- Addressed interface member changes (CS0535)
- Resolved type conflicts (CS0436)
- RhinoMocks → Moq conversion (6 projects, 8 test files)
- NUnit assertions upgrade (NUnit 3 → NUnit 4)
- Test infrastructure updates
- Removed Win32/x86/AnyCPU platform configurations
- Enforced x64 platform across all projects
- Updated native VCXPROJ files
- CI enforcement of x64-only builds
- Manifest generation implementation
- COM registration elimination
- Test host creation for reg-free testing
- Complete MSBuild Traversal SDK implementation
- Legacy build path removal
- Build script modernization
- Documentation completion
- Legacy file cleanup
- Build validation
- Convergence Specs: Implemented Specs 002, 003, 004, 006
- RegFree Overhaul: Managed assembly support, tooling suite
- Critical Fixes: GDI double-buffering for black screen regression
- CPM Migration: All NuGet packages centralized to
Directory.Packages.props - Binding Redirect Cleanup: Stripped manual
<bindingRedirect>entries from 7+App.configfiles - Stale DLL Detection: Unified single-pass pre-build validation via
Remove-StaleDlls.ps1 - Dead Target Removal: TeamCity download targets removed from
mkall.targets
- LT-22382: Stale/mismatched DLL detection and Newtonsoft version conflict
- LT-22392: Memory leak in
TextsTriStateTreeView(with regression test) - LT-22393: Removed stale
DistFiles/Aga.Controls.dllconflicting with NuGet version - LT-22394:
System.Security.Permissions/DotNetZipdependency pinning - LT-22395: Missing information dialog on Text Chart tab
- LT-22384: Stack overflow error resolution
- LT-22414: Morph Type slice rebuild after SwapValues
- XPath Safety: Fixed XPath injection in
Directory.Build.targetsand XCore lookups
- Automation First: Created Python scripts for bulk conversions
- Systematic Approach: Tackled one error category at a time
- Comprehensive Testing: Validated each phase before proceeding
- Clear Documentation: Maintained detailed records of all changes
- Reversibility: Kept commits atomic for easy rollback if needed
All FieldWorks C# projects have been converted from legacy .NET Framework format to modern SDK-style format.
Automated Conversion via Build/convertToSDK.py:
- Detected project dependencies automatically
- Converted assembly references to ProjectReference or PackageReference
- Preserved conditional property groups
- Set proper SDK type (standard vs. WindowsDesktop for WPF/XAML)
- Handled GenerateAssemblyInfo settings
Key SDK Features Enabled:
- Implicit file inclusion (no manual
<Compile Include>needed) - Simplified project structure
- PackageReference instead of packages.config
- Automatic NuGet restore
- Better incremental build support
| Category | Count | Notable |
|---|---|---|
| Build Infrastructure | 3 | FwBuildTasks, NUnitReport, NativeBuild (NEW) |
| Core Libraries | 18 | FwUtils, xCore, RootSite, FwCoreDlgs, XMLUtils, etc. |
| UI Controls | 8 | FwControls, Widgets, XMLViews, DetailControls, FlexUIAdapter |
| LexText Components | 18 | LexEdDll, MorphologyEditorDll, ITextDll, ParserCore, Discourse |
| Plugins and Tools | 12 | ParatextImport, FixFwData, UnicodeCharEditor, GenerateHCConfig |
| Utilities | 7 | Sfm2Xml, ConvertSFM, ComManifestTestHost (NEW) |
| External Libraries | 7 | ScrChecks, ObjectBrowser, Converter suite |
| Applications | 2 | FieldWorks.csproj (main app), FxtExe.csproj |
| Test Projects | 46 | All follow <Component>Tests.csproj pattern; 6 migrated RhinoMocks → Moq |
All projects use Microsoft.NET.Sdk (or Microsoft.NET.Sdk.WindowsDesktop for WPF) targeting net48 / x64. Key properties:
GenerateAssemblyInfo— set per-project based on whether customAssemblyInfo.csattributes exist<Compile Remove="ProjectTests/**" />— excludes co-located test folders from production assembly- Package versions managed centrally via
Directory.Packages.props(see CPM)
| Package | Version | Notes |
|---|---|---|
SIL.Core / SIL.Core.Desktop |
17.0.0-* | Wildcard pre-release |
SIL.LCModel / SIL.LCModel.Core / SIL.LCModel.Utils |
11.0.0-* | Wildcard pre-release |
System.Resources.Extensions |
8.0.0 | Upgraded from 6.0.0 (NU1605 fix) |
NUnit |
4.4.0 | Upgraded from 3.x |
Moq |
4.20.70 | Replaced RhinoMocks |
Status: ✅ Complete - All NuGet packages managed centrally
All NuGet package versions are now centralized in Directory.Packages.props files, eliminating version drift across 110+ projects. Individual .csproj files use <PackageReference> without explicit Version= attributes; versions are resolved from the central file.
FieldWorks/
├── Directory.Packages.props # Root CPM file (all shared packages)
├── Build/Src/Directory.Packages.props # Build-tool-specific overrides
└── FLExInstaller/Directory.Packages.props # Installer-specific overrides
- Created
Directory.Packages.propsat the repo root with 80+ package version entries - Stripped explicit
Version=attributes from all<PackageReference>items across 108 project files - Created
scripts/Agent/Migrate-ToCpm.ps1automation script for bulk migration - Per-layer overrides for build tools and installer projects that require different versions
With CPM ensuring all projects resolve to the same package version, most manual <bindingRedirect> entries in App.config files became unnecessary:
- 7
App.configfiles simplified — ~140 lines of binding redirects removed - Files affected:
FieldWorks/App.config,AppForTests.config,GenerateHCConfig/App.config,LCMBrowser/App.config,ParaText8PluginTests/App.config,UnicodeCharEditor/App.config - Stale CPM migration artifacts (leftover
packages.config) deleted
- Single source of truth for package versions — no more NU1605 version mismatch warnings
- Easier upgrades — change one line to update a package across all projects
- Eliminated binding redirects — consistent resolution removes the need for manual overrides
- Clearer dependency audit — all package versions visible in one file
Status: ✅ Complete - All builds use traversal SDK
Core Files:
-
FieldWorks.proj- Main traversal orchestrator (NEW)- Defines 21 build phases
- Declarative dependency ordering
- 110+ projects organized by dependency layer
-
Build/InstallerBuild.proj- Installer build entry point (NEW)- Hosts BuildInstaller target for WiX MSI/bundle builds
- Package restore is now handled directly by build.ps1 (
dotnet restore)
-
Build/Src/NativeBuild/NativeBuild.csproj- Native build wrapper (NEW)- Bridges traversal SDK and native C++ builds
- Referenced by FieldWorks.proj Phase 2
21 ordered build phases defined in FieldWorks.proj — native C++ first (Phase 2), managed code (Phases 3–14), tests last (Phases 15–21). See the file for details.
build.ps1 (Windows PowerShell):
- Before: 164 lines with
-UseTraversalflag and legacy paths - After: ~876 lines, always uses traversal
- Automatically bootstraps FwBuildTasks
- Initializes VS Developer environment
- Supports
/mparallel builds - Stale DLL detection: Runs
Remove-StaleDlls.ps1pre-build to catch version-mismatched binaries - Diagnostics config: Optionally copies dev trace config for Debug builds (
-TraceCrashesorUseDevTraceConfig) - Installer support:
-BuildInstallerflag triggers full installer build pipeline
Note: build.sh is not supported in this repo (FieldWorks is Windows-first). Use .\build.ps1.
Removed Parameters:
-UseTraversal(now always on)-Targets(usemsbuild Build/InstallerBuild.proj /t:TargetName)
Build/mkall.targets - Native C++ orchestration:
- Removed: 210 lines of legacy targets
mkall,remakefw*,allCsharp,allCpp(test variants)- PDB download logic (SDK handles automatically)
- Symbol package downloads
- TeamCity-specific download targets (77 lines of dead code)
- Kept:
allCppNoTesttarget for native-only builds
Build/Installer.targets - Installer builds:
- Added:
BuildFieldWorkstarget that callsFieldWorks.proj - Removed: Direct
remakefwcalls - Now integrates with traversal build system
Build/RegFree.targets - Registration-free COM:
- Generates application manifests post-build
- Handles COM class/typelib/interface entries
- Integrated with EXE projects via BuildInclude.targets
Build commands: see .github/instructions/build.instructions.md or build.ps1 -Help.
Status: ✅ Complete - All x86/Win32/AnyCPU configurations removed
1. Solution Platforms (FieldWorks.sln):
- Removed: Debug|x86, Release|x86, Debug|AnyCPU, Release|AnyCPU, Debug|Win32, Release|Win32
- Kept: Debug|x64, Release|x64
2. C# Projects: <PlatformTarget>x64</PlatformTarget> + <Prefer32Bit>false</Prefer32Bit> in Directory.Build.props
3. Native C++ Projects: 8 VCXPROJ files — Win32 configurations removed, MIDL updated for 64-bit
4. CI Enforcement: ./build.ps1 -Configuration Debug -Platform x64 in .github/workflows/CI.yml
Status: ✅ Complete - Comprehensive Native + Managed Support
Key Components:
-
RegFree MSBuild Task (
Build/Src/FwBuildTasks/RegFree.cs)- New: Uses
System.Reflection.Metadatafor lock-free inspection - New: Supports managed assemblies (
[ComVisible],[Guid]) - Generates
<file>,<comClass>,<typelib>,<clrClass>entries - Handles dependent assemblies and proxy stubs
- New: Uses
-
Tooling Suite (
scripts/regfree/)audit_com_usage.py: Scans codebase for COM instantiation patternsextract_clsids.py: Harvests CLSIDs/IIDs from sourcegenerate_app_manifests.py: Automates manifest creation for apps
-
Build Integration (
Build/RegFree.targets):- Triggered post-build for WinExe projects
- Processes all native DLLs and managed assemblies in output directory
- Generates
<ExeName>.exe.manifest
Generated manifests: FieldWorks.exe.manifest (main app + managed COM), FwKernel.X.manifest (proxy stubs), Views.X.manifest (27+ COM classes). Installer includes manifests with zero COM registration actions. ComManifestTestHost enables reg-free COM testing.
Status: ✅ Complete — 6 projects, 8 test files converted via convert_rhinomocks_to_moq.py
Key pattern changes:
MockRepository.GenerateStub<T>()→new Mock<T>().Object.Stub(...).Return(v)→.Setup(...).Returns(v)GetArgumentsForCallsMadeOn→Callback<T>capture (manual conversion)- Out parameters:
.OutRef(...)→ inlineoutvariables in.Setup(...)
See RHINOMOCKS_TO_MOQ_MIGRATION.md for the full pattern catalog.
Status: ✅ Complete — All 46 test projects upgraded (NUnit 4.4.0, NUnit3TestAdapter 5.2.0)
Assertion syntax updated via Build/convert_nunit.py:
Assert.IsTrue(x)→Assert.That(x, Is.True)(and similar forIsFalse,IsNull,AreEqual, etc.)- Constraint-model assertions (
Assert.That) are backwards-compatible and preferred
~80 compilation errors fixed across 12 categories. See MIGRATION_ANALYSIS.md for full code examples.
| # | Error | Symptom | Fix |
|---|---|---|---|
| 1 | NU1605 — Package version mismatch | Transitive downgrade warnings | Align explicit versions (e.g., System.Resources.Extensions 6.0→8.0) |
| 2 | CS0579 — Duplicate AssemblyInfo | SDK auto-generates when GenerateAssemblyInfo=false |
Set to true per-project; remove manual AssemblyInfo.cs duplicates |
| 3 | CS0103 — XAML codegen missing | InitializeComponent() not generated |
Use Microsoft.NET.Sdk.WindowsDesktop + <UseWPF>true</UseWPF> |
| 4 | CS0535 — Interface member missing | IThreadedProgress.Canceling added in SIL package update |
Implement new members in all implementations |
| 5 | CS0436 — Type conflicts | Test files compiled into production assembly | <Compile Remove="ProjectTests/**" /> |
| 6 | CS0234/CS0246 — Missing packages | Package references lost during conversion | Add explicit <PackageReference> entries |
| 7 | CS0738 — Generic interface mismatch | Mock used ITextRepository instead of IRepository<IText> |
Use correct generic interfaces |
| 8 | NU1503 — C++ NuGet warnings | NuGet restore skips VCXPROJ | Suppress with <NoWarn>NU1503</NoWarn> |
| 9 | GDI+ / GDI rendering regression | System.Drawing.Bitmap pixel format incompatible with GDI BitBlt |
Replace GDI+ double-buffer with native GDI DC |
| 10 | XPath injection | Apostrophes in tool/clerk IDs break XPath predicates | concat() quoting in build targets and XCore lookups |
| 11 | Stale DLL version mismatch | Old DLLs in Output/ cause FileLoadException at runtime |
Pre-build scanner Remove-StaleDlls.ps1 |
| 12 | Transitive dependency conflicts | Runtime conflicts (e.g., System.Security.Permissions) |
Global version pins in Directory.Build.props; remove stale DistFiles/ copies |
The SDK migration exposed several latent bugs and dependency conflicts that were masked by the legacy build system. These were discovered and fixed during post-migration stabilization:
| Ticket | Summary | Root Cause | Fix |
|---|---|---|---|
| LT-22382 | Newtonsoft.Json version conflict; stale binaries | Mismatched DLL versions in Output/ vs NuGet packages |
Created Remove-StaleDlls.ps1; pinned package versions in CPM |
| LT-22383 | Interlinear text missing (Gecko dependency) | Missing assembly reference after SDK conversion | Added explicit <PackageReference> |
| LT-22384 | Stack overflow error | Recursive call path exposed by changed initialization order | Fixed recursive control initialization |
| Ticket | Summary | Root Cause | Fix |
|---|---|---|---|
| LT-22378 | Duplicate Create + Edit button on new entry | UI state not reset after SDK control initialization changes | Fixed button state management |
| LT-22395 | Information dialog missing on Text Chart tab | MessageBox trigger key change caused "Do not show again" to persist |
Renamed trigger from TextChartNewFeature to TextChartTemplateWarning to reset user preference |
| LT-22414 | Morph Type slice disappears after SwapValues | Slice not rebuilt after property swap | Added explicit rebuild of Morph Type slice after SwapValues |
| Ticket | Summary | Root Cause | Fix |
|---|---|---|---|
| LT-22392 | Memory leak in TextsTriStateTreeView |
Event handlers not unsubscribed on dispose | Fixed disposal pattern; added regression test (LT-22396) |
| LT-22393 | Aga.Controls.dll version conflict |
DistFiles/Aga.Controls.dll (1.7.0.0) conflicted with NuGet 1.7.7.0 |
Removed stale DistFiles/ copy |
| LT-22394 | System.Security.Permissions conflict |
DotNetZip pulling different transitive version |
Added global version pin (9.0.9) in Directory.Build.props |
- SDK-style projects change assembly resolution order — stale binaries in output directories can shadow NuGet packages
- Transitive dependency conflicts only manifest at runtime — compile-time success doesn't guarantee runtime correctness
- UI initialization order can change subtly when project structure changes — regression testing is critical
DistFiles/binaries must be audited after moving to NuGet — old copies cause silent conflicts
Bin/*.bat,Bin/*.cmd- Pre-MSBuild build entry pointsmkall.bat,RemakeFw.bat,mk*.batCollectUnit++Tests.bat,BCopy.bat- Duplicated functionality now in mkall.targets
Bin/*.exe,Bin/*.dll- Old build/test utilities- Replaced by modern SDK tooling or NuGet packages
Build/FieldWorks.proj(non-SDK) - Replaced byBuild/InstallerBuild.projfor installer targets; package restore moved to build.ps1Build/native.proj- Optional wrapper (removed)- Legacy project files from non-SDK era
- Old packages.config files
- Legacy NuGet.config entries
- Obsolete .targets includes
All legacy references updated to point to new paths
- nmock source (6 projects) - Replaced by Moq
- Legacy test helpers - Modernized
From Build/mkall.targets (210 lines removed):
mkall- Use traversal build via build.ps1remakefw- Use traversal buildremakefw-internal,remakefw-ci,remakefw-jenkins- No longer neededallCsharp- Managed by traversal SDKallCpp- UseallCppNoTestinsteadrefreshTargets- UseGenerateVersionFilesif needed- PDB download logic - SDK handles automatically
- Symbol package downloads - No longer needed
Before Migration:
- Multiple build entry points (batch, PowerShell, Bash, MSBuild)
- Scattered build logic across 30+ files
- Manual dependency management
- Platform-specific quirks
After Migration:
- Single entry point:
build.ps1→FieldWorks.proj - Centralized build logic in traversal SDK
- Automatic dependency resolution
- Consistent build experience via traversal ordering
| Script | Purpose |
|---|---|
Build/convertToSDK.py |
Bulk-converted 119 .csproj files to SDK format with intelligent dependency mapping |
Build/convert_nunit.py |
Automated NUnit 3 → 4 assertion syntax (20+ patterns) |
convert_rhinomocks_to_moq.py |
Automated RhinoMocks → Moq conversion |
add_package_reference.py |
Bulk-add PackageReference to multiple projects |
scripts/Agent/Migrate-ToCpm.ps1 |
Automated migration to Central Package Management |
| Script | Purpose |
|---|---|
Build/Agent/Remove-StaleDlls.ps1 |
Pre-build scanner: compares Output/ DLLs against Directory.Packages.props versions |
Build/Agent/Verify-FwDependencies.ps1 |
Environment validation (VS, .NET SDK, native toolchain, NuGet) |
Created test.ps1 (unified managed + native test runner), installer validation pipeline (scripts/Agent/Invoke-InstallerCheck.ps1 + Hyper-V clean-VM testing), and developer environment automation (Setup-Developer-Machine.ps1, Setup-DefenderExclusions.ps1, Verify-FwDependencies.ps1). WiX 3/6 side-by-side targets maintained for parity validation. VS Code tasks for build, test, installer, worktree management, and multi-agent workflows (.vscode/tasks.json).
| Metric | Value |
|---|---|
| Total commits | 115 |
| Files changed | 728 |
| Projects converted | 119 (111 SDK + 8 VCXPROJ) |
| Production projects | 73 |
| Test projects | 46 |
| Compilation errors fixed | ~80 |
| Files removed | 140 |
| Lines added / removed | ~15,000 / ~18,000 |
| RhinoMocks → Moq projects | 6 → 0 |
| NUnit version | 3.x → 4.4.0 |
| Build entry points | 30+ batch files → 1 (build.ps1) |
| Platforms | x86+x64+AnyCPU → x64 only |
What worked well: Automation-first approach (Python scripts for 90% of conversions), systematic error resolution (one category at a time), incremental validation (no major rollbacks), comprehensive documentation.
Key pitfalls: Transitive dependency conflicts only visible at runtime; SDK auto-include silently compiles test code into production assemblies; RhinoMocks → Moq has no 1:1 mapping for advanced patterns; GenerateAssemblyInfo needs per-project evaluation.
Best practices established:
- Always set
GenerateAssemblyInfoexplicitly per-project - Exclude test directories:
<Compile Remove="ProjectTests/**" /> - Use
Microsoft.NET.Sdk.WindowsDesktopfor WPF projects - Keep
PlatformTarget=x64andPrefer32Bit=falseeverywhere - Use Traversal SDK for multi-project ordering with declared dependencies
- Prefer modern test frameworks (NUnit 4, Moq) for active maintenance
All validation gates passed: clean/release/incremental builds, installer (base + patch on clean VM), full test suite via test.ps1, CI enforcement (x64, manifests, whitespace, commit messages).
| Issue | Resolution |
|---|---|
| Installer testing on clean machines | Built Hyper-V snapshot pipeline (Invoke-InstallerCheck.ps1) |
| Native + managed test orchestration | Created unified test.ps1 (managed VSTest + native C++) |
| Reg-free COM in test context | Created ComManifestTestHost for manifest-aware test execution |
| Document | Purpose |
|---|---|
MIGRATION_ANALYSIS.md |
Detailed error fixes and code examples |
TRAVERSAL_SDK_IMPLEMENTATION.md |
21-phase traversal SDK architecture |
RHINOMOCKS_TO_MOQ_MIGRATION.md |
Mock framework conversion patterns |
MIGRATION_FIXES_SUMMARY.md |
Issue breakdown and patterns |
Docs/traversal-sdk-migration.md |
Developer migration guide |
Docs/64bit-regfree-migration.md |
64-bit / reg-free COM plan |
.github/instructions/build.instructions.md |
Build commands and usage |
119 projects converted, 140 legacy files removed, 15+ runtime bugs fixed (LT-22378–LT-22414), zero legacy build paths remain. The migration is operationally complete.
Document Version: 2.0 — Postmortem / Blame Index Last Updated: 2026-02-17 Migration Status: ✅ COMPLETE