Skip to content

Retarget all managed projects to .NET 10 (net10.0-windows10.0.17763.0)#1947

Closed
tyrielv wants to merge 16 commits into
microsoft:masterfrom
tyrielv:tyrielv/net10-tfm-retarget
Closed

Retarget all managed projects to .NET 10 (net10.0-windows10.0.17763.0)#1947
tyrielv wants to merge 16 commits into
microsoft:masterfrom
tyrielv:tyrielv/net10-tfm-retarget

Conversation

@tyrielv
Copy link
Copy Markdown
Contributor

@tyrielv tyrielv commented Apr 16, 2026

Phase 3 of the .NET 10 migration: TFM retarget, API migrations, and build/CI updates consolidated into a single PR.

Foundation:

  • Pin .NET 10 SDK in global.json (10.0.100 with latestFeature rollback)
  • Centralize TargetFramework in Directory.Build.props
  • Bump default dev version to 0.3.0.1 (official builds use 2.0.x.x)
  • Update MSBuild SDK versions (Traversal 4.1.0, NoTargets 3.7.0)
  • Update CI to .NET 10 SDK and windows-2025 runner

Package updates:

  • Microsoft.Data.Sqlite 2.2.4 -> 9.0.4
  • Microsoft.Build.* 16.0.461 -> 17.12.6
  • Microsoft.Windows.ProjFS 1.1.19156.1 -> 2.1.0
  • System.ServiceProcess.ServiceController 4.5.0 -> 9.0.4
  • Add System.Diagnostics.EventLog, System.Management, System.IO.Pipes.AccessControl
  • Remove GVFS.ProjFS package (ProjFS is now a Windows OS feature)
  • Replace CommandLineParser and Newtonsoft.Json stale references

API migrations:

  • HttpUtility.UrlEncode -> WebUtility.UrlEncode (OrgInfoApiClient)
  • Assembly.Location -> Environment.ProcessPath (ProcessHelper, HooksInstaller)
  • NamedPipeServerStream ctor -> NamedPipeServerStreamAcl.Create (WindowsPlatform)
  • Directory.Get/SetAccessControl -> DirectoryInfo extensions (WindowsFileSystem, GVFSService)
  • Directory.CreateDirectory(path, security) -> DirectorySecurity.CreateDirectory(path)
  • Remove HttpRequestor machine.config lock and ServicePointManager usage
  • Remove UseDefaultCredentials from HttpClientHandler
  • Remove all .NET Framework assembly references (System.Web, System.Net.Http, etc.)

Scripts and installer:

  • Update output paths from net471 to net10.0-windows10.0.17763.0\win-x64
  • Remove ProjFS native DLL bundling from layout.bat (OS feature now)
  • Update installer MinVersion to 10.0.17763

Test fixes:

  • Add missing IVirtualizationInstance members to MockVirtualizationInstance
  • Remove stale GVFS.Service.UI project reference from UnitTests

All 803 unit tests pass (792 passed, 11 pre-existing skips).

Assisted-by: Claude Opus 4.6

@tyrielv tyrielv force-pushed the tyrielv/net10-tfm-retarget branch 7 times, most recently from 5efd7d7 to b58e509 Compare April 20, 2026 18:45
Phase 3 of the .NET 10 migration: TFM retarget, API migrations, and
build/CI updates consolidated into a single PR.

Foundation:
- Pin .NET 10 SDK in global.json (10.0.100 with latestFeature rollback)
- Centralize TargetFramework in Directory.Build.props
- Bump default dev version to 0.3.0.1 (official builds use 2.0.x.x)
- Update MSBuild SDK versions (Traversal 4.1.0, NoTargets 3.7.0)
- Update CI to .NET 10 SDK and windows-2025 runner

Package updates:
- Microsoft.Data.Sqlite 2.2.4 -> 9.0.4
- Microsoft.Build.* 16.0.461 -> 17.12.6
- Microsoft.Windows.ProjFS 1.1.19156.1 -> 2.1.0
- System.ServiceProcess.ServiceController 4.5.0 -> 9.0.4
- Add System.Diagnostics.EventLog, System.Management, System.IO.Pipes.AccessControl
- Remove GVFS.ProjFS package (ProjFS is now a Windows OS feature)
- Replace CommandLineParser and Newtonsoft.Json stale references

API migrations:
- HttpUtility.UrlEncode -> WebUtility.UrlEncode (OrgInfoApiClient)
- Assembly.Location -> Environment.ProcessPath (ProcessHelper, HooksInstaller)
- NamedPipeServerStream ctor -> NamedPipeServerStreamAcl.Create (WindowsPlatform)
- Directory.Get/SetAccessControl -> DirectoryInfo extensions (WindowsFileSystem, GVFSService)
- Directory.CreateDirectory(path, security) -> DirectorySecurity.CreateDirectory(path)
- Remove HttpRequestor machine.config lock and ServicePointManager usage
- Remove UseDefaultCredentials from HttpClientHandler
- Remove all .NET Framework assembly references (System.Web, System.Net.Http, etc.)

Scripts and installer:
- Update output paths from net471 to net10.0-windows10.0.17763.0\win-x64
- Remove ProjFS native DLL bundling from layout.bat (OS feature now)
- Update installer MinVersion to 10.0.17763

Test fixes:
- Add missing IVirtualizationInstance members to MockVirtualizationInstance
- Remove stale GVFS.Service.UI project reference from UnitTests

All 803 unit tests pass (792 passed, 11 pre-existing skips).

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/net10-tfm-retarget branch 13 times, most recently from 6984f48 to b7a0793 Compare April 22, 2026 16:41
NativeAOT compiles managed projects to native binaries, eliminating
the .NET runtime dependency. This fixes CI functional tests which run
on machines without .NET 10 installed.

Changes:
- Add SelfContained=true and PublishAot=true to Directory.Build.props
- Add OptimizationPreference=Speed for performance-critical system component
- Opt out test projects from AOT (NUnit uses reflection for discovery)
- Opt out GVFS.MSBuild (netstandard2.0 build task)
- Switch Build.bat from 'dotnet build' to 'dotnet publish' for managed projects
- Update all script output paths to include publish\ subdirectory
- Pin global.json to SDK 10.0.202 with rollForward=disable

Output: ~20 MB native GVFS.exe, 36.7 MB installer (vs 107 MB self-contained)

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/net10-tfm-retarget branch 8 times, most recently from c272226 to 69c8913 Compare April 23, 2026 19:01
tyrielv added 4 commits April 24, 2026 08:45
Before: Functional tests hung indefinitely in CI with no output, making it
impossible to diagnose failures. The gvfs process calls had no timeout and
stdout/stderr were not streamed to the CI log.

Changes:
- Add [CI-DEBUG] logging to CloneAndMount and gvfs process invocations
- Add 5-minute timeout per gvfs process invocation in GVFSProcess.CallGVFS
- Redirect and stream stderr via BeginErrorReadLine for real-time CI output
- Add timeout-minutes: 60 to functional-tests.yaml test step
- Add --workers=1 to CI test runner for sequential execution

Result: Tests no longer hang indefinitely. CI logs show real-time gvfs
output for diagnosis. 48/56 tests passed in first run with output, revealing
6 specific failures to investigate (previously all 56 timed out silently).

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Before: GVFS.Mount crashed with ArgumentNullException in
OnPlaceholderFileCreated/OnPlaceholderFolderCreated/OnPlaceholderFileHydrated
when ProjFS passed null for triggeringProcessImageFileName. This caused 5 of
6 CI test failures (4 Native_ProjFS tests + ResetHardDirectoryWithOneFileWrite).

Root cause: The new pure-C# ProjFS managed API (Microsoft.Windows.ProjFS 2.1.0)
uses Marshal.PtrToStringUni(pData->TriggeringProcessImageFileName) which returns
null when the native pointer is IntPtr.Zero (process ID 0, kernel operations).
The old C++/CLI wrapper explicitly checked for NULL and returned String.Empty:
  (callbackData->TriggeringProcessImageFileName != NULL)
      ? gcnew String(...) : String::Empty

ConcurrentDictionary.AddOrUpdate does not accept null keys, so the null
propagated up and crashed the mount process.

Fix: Null-coalesce to string.Empty in all three AddOrUpdate call sites,
matching the old C++/CLI behavior. This is defense-in-depth; the upstream
fix belongs in Microsoft.Windows.ProjFS (tracked but not yet filed).

Result: All 5 mount-crash test failures resolved. Slice 1 (which had the
same crash pattern) now passes 4/4 in CI.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Before: TruncatedObjectRedownloaded test failed 4/4 in CI. In the previous
CI run, file content had null bytes; in the latest run, the CorruptObjects
count assertion failed (expected initialCount+1, got initialCount).

Root cause: .NET 10's DeflateStream silently returns partial data on truncated
zlib input instead of throwing InvalidDataException (behavioral change from
.NET Framework 4.7.1). Verified with a standalone test:
  - .NET 4.7.1: DeflateStream throws InvalidDataException on truncated zlib
  - .NET 10: DeflateStream returns 30 of 45 expected bytes, no exception

In GitRepo.GetLooseBlobStateAtPath, the code relied on InvalidDataException
to detect corrupt objects and move them to the CorruptObjects folder. Without
the exception, the truncated object was treated as valid (LooseBlobState.Exists)
and partial/corrupt content was served to the caller.

Fix: Wrap the DeflateStream in a CountingStream that tracks bytes read. After
writeAction completes, compare actual bytes read to the size declared in the
git object header. If fewer bytes were read, mark the object as corrupt.
CountingStream fully delegates all operations to the inner stream, only adding
byte counting on Read/ReadByte.

Result: TruncatedObjectRedownloaded (slice 1) now passes 4/4 in CI.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Documentation for picking up the .NET 10 NativeAOT migration work from a
clean slate. Includes:

- docs/net10-migration-status.md: Current CI results, remaining failures
  with analysis, .NET 10 behavioral changes discovered, build commands,
  deferred items
- docs/miniksa-reference.md: What was applied from miniksa's prototype
  branch, what wasn't applied yet, and what original work we did beyond
  his branch

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/net10-tfm-retarget branch from 69c8913 to 380033d Compare April 24, 2026 15:48
SDK 10.0.202 no longer available via winget; 10.0.203 is current servicing
release. Same feature band, runtime updated from 10.0.5 to 10.0.7.

Also required reinstalling NuGet credential provider for ARM64 — the
previous x64-only binary fails with 0x800700C1 on ARM64 .NET 10.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
tyrielv added 6 commits April 27, 2026 14:07
HydrateFile used direct P/Invoke to kernel32 CreateFile/ReadFile with
incorrect parameter types (uint/int for pointer-sized params). Under
NativeAOT with high concurrency (ProcessorCount*2 threads), this caused
intermittent ACCESS_VIOLATION (0xC0000005) crashes in CI slices 2 and 3.

Replace with managed FileStream which uses the runtime's own validated
interop layer. Benchmarked at equivalent throughput (~36-40K files/s)
in the multi-threaded scenario matching HydrateFilesStage usage.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
.NET Framework defaults ProcessStartInfo.UseShellExecute to true
(uses ShellExecuteEx, no handle inheritance) while .NET 10 defaults
it to false (uses CreateProcess, handles inherited). Without this,
GVFS.Mount.exe inherits the caller's stdout/stderr pipe handles and
holds them open as a daemon, causing callers that read to EOF (e.g.
git's hook runner via ProcessHelper.Run) to block indefinitely.

This was the root cause of the slice 9 functional test timeout: the
post-command hook's gvfs mount spawns GVFS.Mount.exe which inherits
the hook's stderr handle. Git waits for the hook to close stderr,
but it never does because the mount daemon keeps the handle alive.

Credit: miniksa's net10-nativeaot prototype had this fix; it was
missed during the migration.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
.NET 10's File.SetLastWriteTime uses FILE_WRITE_ATTRIBUTES without
triggering ProjFS hydration, unlike .NET Framework 4.7.1 which fully
hydrated the placeholder. This left the file as a virtual placeholder
in git's index, causing 'pathspec did not match' on checkout.

Simulate the user scenario (edit + undo) by reading and writing the
file back unchanged before adjusting the timestamp. This hydrates
the placeholder into a full file, matching the real-world state a
user would be in.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Port 7 changes from miniksa/user/miniksa/net10-nativeaot that were missed
during our net10-tfm-retarget migration:

- ProcessHelper: null-safety fallback to AppContext.BaseDirectory (3 methods)
- InProcessMount: wrap Console.Title in try-catch for headless processes
- HttpRequestor: switch to SocketsHttpHandler with connection pool tuning
- GitAuthentication: add ConfigureSocketsHandlerSslIfNeeded for SocketsHttpHandler
- Settings: null-safety fallback for Environment.ProcessPath
- WindowsPhysicalDiskInfo: replace WMI (System.Management) with kernel32 P/Invoke
- Remove System.Management package dependency

The Console.Title fix is in the same mount startup path affected by the
UseShellExecute=true fix and may have contributed to slice 9 hangs.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyler Vella <tyrielv@gmail.com>
Port remaining NativeAOT enablement changes from miniksa's prototype:

Build infrastructure:
- Directory.Build.targets: _CopyNativeHooks target replicates production
  layout during dev builds (native C++ hooks + managed peer exes)
- Directory.Packages.props: remove System.Management (replaced by P/Invoke),
  add Microsoft.NET.Test.Sdk for CLI test project

CLI backward-compatibility tests (88 tests, all passing):
- FastFetchCliTests: 17 tests validating option aliases and defaults
- GvfsMainCliTests: 15+ verb tests for clone/mount/prefetch/sparse/etc
- GvfsMountCliTests: mount defaults (verbosity, keywords, StartedByService)
- Already caught a real bug: --Allow-index-metadata case mismatch

InternalsVisibleTo + BuildRootCommand wrappers:
- Expose BuildRootCommand from Program classes for CLI test access
- Added to GVFS, GVFS.Mount, FastFetch projects

Other changes:
- GVFSEnlistment: reduce mount poll interval 500ms→100ms
- FastFetchVerb: fix --Allow → --allow option name (case-sensitive)
- doc/design-net10-nativeaot.md: miniksa's 6-phase migration design doc
- scripts/publish-aot.ps1: end-to-end AOT publish pipeline for dev loop

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyler Vella <tyrielv@gmail.com>
Add explicit parameterless constructors and { get; set; } to
NamedPipeMessages Response classes that had only parameterized
constructors. System.Text.Json source generators require a
parameterless constructor for deserialization.

Also add miniksa's overview slide deck doc for review.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyler Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/net10-tfm-retarget branch 2 times, most recently from 0b0d956 to 562ef62 Compare April 29, 2026 17:55
.NET 10's FileInfo property setters (CreationTime, LastAccessTime,
LastWriteTime, Attributes) no longer open write handles that trigger ProjFS
hydration. Only actual file content I/O (read+write) causes hydration.

The original ExpandedFileAttributesAreUpdated test relied on CreationTime's
setter triggering hydration as a side effect. Replace it with two tests:

1. PlaceholderMetadataSurvivesHydration:
   Sets all metadata (timestamps + Hidden) on a ProjFS placeholder, verifies
   the file remains a placeholder, asserts properties took effect, then
   hydrates via FileStream read+write and verifies CreationTime and Hidden
   survived the conversion. (LastAccessTime/LastWriteTime are inherently
   updated by the hydration I/O and cannot be asserted post-hydration.)

2. HydratedFileTimestampsAndAttributesAreUpdated:
   Hydrates a file via read+write first, asserts it is hydrated, then sets
   all timestamps and Hidden, and verifies everything sticks.

Additional .NET 10 behavioral changes documented:
- File.WriteAllText uses FileMode.Create which fails on Hidden files
- FileStream with FileMode.Open works on Hidden files

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/net10-tfm-retarget branch from 562ef62 to 95d2b12 Compare April 29, 2026 18:40
tyrielv added 2 commits April 29, 2026 13:12
The AOT enablement commit added a solution folder named 'GVFS' to hold
GVFS.CommandLine.Tests, but that name conflicts with the existing GVFS.csproj
project entry also named 'GVFS'. Remove the solution folder and its nesting
entry; the test project remains in the solution at the root level.

Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv closed this Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant