Skip to content

.NET 10 NativeAOT migration#1953

Merged
tyrielv merged 8 commits intomicrosoft:masterfrom
tyrielv:tyrielv/net10-pr
May 4, 2026
Merged

.NET 10 NativeAOT migration#1953
tyrielv merged 8 commits intomicrosoft:masterfrom
tyrielv:tyrielv/net10-pr

Conversation

@tyrielv
Copy link
Copy Markdown
Contributor

@tyrielv tyrielv commented Apr 29, 2026

.NET 10 NativeAOT Migration

Retarget VFSForGit from .NET Framework 4.7.1 to .NET 10 with NativeAOT self-contained deployment. Native binaries eliminate the .NET runtime dependency (~20 MB GVFS.exe, 36.7 MB installer vs 107 MB self-contained).

Based on this branch by @miniksa

Commit walkthrough

# Commit Summary Files What to look for
1 66334474 Retarget to .NET 10 + NativeAOT 33 TFM change, package updates, Directory.Build.props/targets for AOT, Build.bat 3-step build, CI yaml, installer, script output paths
2 f2df4049 Replace System.Reflection.Assembly 7 Assembly.Location → Environment.ProcessPath with null guard, AppDomain.FriendlyName for process name
3 c5b7ca75 Replace changed .NET 10 APIs 10 NamedPipeServerStreamAcl, DirectorySecurity instance methods, Uri.EscapeDataString, UseShellExecute, CountingStream for DeflateStream truncation
4 bb787608 Replace WMI with kernel32 P/Invoke 1 WindowsPhysicalDiskInfo: DeviceIoControl replaces System.Management (COM interop incompatible with AOT)
5 c36bc237 ProjFS null guard 1 FileSystemCallbacks: null-coalesce triggeringProcessImageFileName (ProjFS v2.1.0 returns null for kernel ops)
6 97fa1e13 Replace HTTP stack 3 SocketsHttpHandler with connection pool config, remove UseDefaultCredentials/ServicePointManager, X509CertificateLoader, remove machine.config lock check
7 33d3b952 JSON source generation for AOT 6 GVFSJsonContext (25+ types), TypeInfoResolverChain, parameterless constructors for STJ deserialization, ServiceJsonContext for RepoRegistration (cross-assembly AOT fix), ConsoleOutputPayload visibility
8 62c5c6c6 Functional test adaptations 11 ProjFS hydration tests rewritten (FileInfo setters no longer trigger hydration), fix stdout truncation in WaitForExit (root cause of flaky HealthTests/TombstoneTests/GitCommandsTests), HealthTests output validation, diagnostic logging

Retarget from net471 to net10.0-windows10.0.17763.0 across all managed
projects. Enable NativeAOT self-contained deployment, eliminating the
.NET runtime dependency.

Build infrastructure:
- global.json: pin SDK 10.0.203
- Directory.Build.props: centralized TFM, SelfContained, PublishAot,
  OptimizationPreference=Speed
- Directory.Build.targets: AOT build targets; opt out test projects and
  GVFS.MSBuild (netstandard2.0) from AOT
- Build.bat: 3-step build (dotnet restore, VS MSBuild for C++, dotnet
  publish for managed AOT binaries)
- publish-aot.ps1: standalone script for local AOT publish testing
  (CI uses Build.bat; this script is for dev iteration)
- Update output paths in all scripts (net471 -> net10.0-.../publish)
- Update CI to .NET 10 SDK and windows-2025 runner
- Update installer MinVersion to 10.0.17763

Package updates:
- Microsoft.Windows.ProjFS 1.1 -> 2.1.0: pure C# P/Invoke replacing
  C++/CLI interop, required for NativeAOT compatibility
- Microsoft.Data.Sqlite 2.2.4 -> 9.0.4, Microsoft.Build.* 16 -> 17.12.6
- Add System.Diagnostics.EventLog, System.IO.Pipes.AccessControl:
  previously included in .NET Framework, now separate packages
- Remove GVFS.ProjFS (ProjFS is now a Windows OS feature)

Unit test fixture updates for new ProjFS managed API surface.

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

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/net10-pr branch 2 times, most recently from 692f5c2 to 5335135 Compare April 30, 2026 19:51
tyrielv and others added 5 commits May 1, 2026 11:01
Assembly.Location returns empty string under NativeAOT since there is no
managed assembly on disk. Assembly.GetName().Version returns null.

- ProcessHelper: use Environment.ProcessPath with null guard (can be null
  in certain hosting scenarios), fall back to AppContext.BaseDirectory
- HooksInstaller: same Environment.ProcessPath pattern with null guard
- GVFSEnlistment: AppDomain.CurrentDomain.FriendlyName replaces
  Assembly.GetEntryAssembly().GetName() for process name

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
NamedPipeServerStream (WindowsPlatform.cs):
  ACL-accepting constructor removed from .NET Core; use
  NamedPipeServerStreamAcl.Create extension method.

Directory ACL APIs (WindowsFileSystem.cs, GVFSService.Windows.cs):
  Static Directory.GetAccessControl/SetAccessControl and
  Directory.CreateDirectory(path, security) removed from .NET Core;
  replaced with DirectoryInfo instance methods and
  DirectorySecurity.CreateDirectory extension.

Uri escaping (CloneVerb.cs, GVFSVerb.cs, OrgInfoApiClient.cs):
  Uri.EscapeUriString obsoleted in .NET 10 (does not escape '#', '?');
  use Uri.EscapeDataString. HttpUtility.UrlEncode (System.Web) replaced
  with WebUtility.UrlEncode (System.Net).

UseShellExecute (WindowsPlatform.cs, InProcessMount.cs):
  .NET Framework defaults UseShellExecute=true (ShellExecuteEx, no handle
  inheritance). .NET 10 defaults to false (CreateProcess, handles inherited).
  Without this, GVFS.Mount.exe inherits the caller's stdout pipe handle,
  causing callers that read to EOF to block indefinitely.

Truncated loose object detection (GitRepo.cs):
  .NET 10 DeflateStream silently returns partial data on truncated zlib
  instead of throwing InvalidDataException. CountingStream wrapper compares
  actual bytes read to header-declared size to detect corruption.

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
System.Management requires COM interop which is incompatible with
NativeAOT. Replace WMI queries (MSFT_Volume, MSFT_Partition, MSFT_Disk,
MSFT_PhysicalDisk) with direct kernel32 DeviceIoControl calls using
IOCTL_STORAGE_QUERY_PROPERTY and IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
for disk telemetry collection.

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
ProjFS managed API v2.1.0 uses Marshal.PtrToStringUni which returns null
for IntPtr.Zero (kernel operations with PID 0). The old C++/CLI wrapper
returned String.Empty. Null-coalesce to match old behavior in all three
callback sites (OnPlaceholderFileCreated, OnPlaceholderFolderCreated,
OnPlaceholderFileHydrated); ConcurrentDictionary does not accept null keys.

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Replace HttpClientHandler with SocketsHttpHandler for explicit connection
pool lifecycle management: configurable MaxConnectionsPerServer (2x CPU
count), PooledConnectionLifetime, and PooledConnectionIdleTimeout. Remove
UseDefaultCredentials (not supported on SocketsHttpHandler) and
ServicePointManager usage (.NET Framework only).

GitSsl: X509Certificate2(byte[]) constructor obsoleted; use
X509CertificateLoader.LoadCertificate.

GitAuthentication: adapt credential flow for new HTTP handler.

Remove machine.config lock check (.NET 10 does not use machine.config).

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Comment thread GVFS/GVFS.Common/GVFSJsonOptions.cs
tyrielv and others added 2 commits May 4, 2026 11:06
NativeAOT cannot use runtime reflection for JSON serialization.
GVFSJsonContext provides source-generated System.Text.Json serializers
for 25+ types used in named pipe messages and configuration.

GVFSJsonOptions chains source-gen (primary) with reflection fallback
for types not yet in the context, allowing incremental migration.

NamedPipeMessages: add parameterless constructors required by the
source generator's deserialization codegen.

RepoRegistration: add ServiceJsonContext source generator in
GVFS.Service for types that cannot be registered in GVFSJsonContext
(GVFS.Common) due to assembly dependency direction.

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
.NET 10's FileInfo property setters no longer open write handles that
trigger ProjFS placeholder hydration. Adapt tests that relied on this.

BasicFileSystemTests: replace ExpandedFileAttributesAreUpdated with two
focused tests:
- PlaceholderMetadataSurvivesHydration: sets timestamps + Hidden on a
  placeholder, verifies they took effect, hydrates via read+write, and
  asserts CreationTime and Hidden survived the conversion.
- HydratedFileTimestampsAndAttributesAreUpdated: hydrates first, then
  sets all properties and verifies they stick.

GitCommandsTests: ChangeTimestampAndDiff now explicitly hydrates via
read+write before adjusting timestamps, since File.SetLastWriteTime
no longer triggers ProjFS hydration.

GVFSProcess: add 5-minute timeout per gvfs process invocation to
prevent CI hangs. Stream stdout/stderr for real-time CI output.

functional-tests.yaml: reduce mount sleep from 500ms to 100ms,
add timeout-minutes.

Co-authored-by: Michael Niksa <miniksa@microsoft.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/net10-pr branch from 017e547 to 62c5c6c Compare May 4, 2026 18:06
Copy link
Copy Markdown
Contributor

@derrickstolee derrickstolee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working hard on this. I think that if any follow-up work is required, then it would be best to do in a follow-up instead of getting lost in this giant diff.

@tyrielv tyrielv merged commit 50b24e6 into microsoft:master May 4, 2026
45 checks passed
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.

3 participants