Skip to content

Investigate replacing custom NuGet helpers with official IsAotCompatible libraries #309

@richlander

Description

@richlander

Summary

I ran an isolated worktree experiment to test whether the current custom NuGet helper libraries (NuGetFetch / SourceLinkFetch) could be replaced by the official NuGet client libraries now that upstream NuGet.Client marks projects as IsAotCompatible=true.

The result is not yet good enough for dotnet-inspect's NativeAOT use case.

The intended bar here is not just "publishes under NativeAOT", but:

  • libraries built with IsAotCompatible
  • zero trim/AOT warnings in our scenario
  • working real remote operations at runtime under NativeAOT

What I tested

In a worktree on branch experiment/official-nuget-aot, I created two small benchmark harnesses that exercised the same kinds of remote package operations this app depends on:

  1. search latest package version
  2. list package versions
  3. download a .nupkg

Package used for comparison: Newtonsoft.Json (pinned download version 13.0.3)

Stacks compared:

  • Current custom path: NuGetFetch via DotnetInspector.Packages
  • Official path: NuGet.Protocol 7.3.0

Upstream signal

NuGet.Client does now opt into AOT-compatibility metadata:

<PropertyGroup Condition="'$(IsAotCompatible)' == '' And '$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
  <IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

Source: NuGet.Client/src/NuGet.Core/Directory.Build.targets

That is encouraging, but it did not translate into a clean warning-free or runtime-successful NativeAOT result in this experiment.

NativeAOT publish outcome

Current custom path (NuGetFetch)

  • NativeAOT publish: success
  • Remote operations at runtime: success
  • Published executable size: 8,799,136 bytes
  • Publish directory size: 39,092,224 bytes

Measured NativeAOT timings (5 iterations):

  • search latest: 144.33 ms avg
  • list versions: 72.90 ms avg
  • download nupkg: 340.74 ms avg

Official path (NuGet.Protocol 7.3.0)

  • NativeAOT publish: success, but with warnings
  • Warnings included:
    • IL2104: NuGet.Protocol produced trim warnings
    • IL2104: NuGet.Configuration produced trim warnings
    • IL2104 / IL3053: Newtonsoft.Json produced trim/AOT analysis warnings
  • Remote operations at runtime: failed
  • Published executable size: 15,783,896 bytes
  • Publish directory size: 77,299,712 bytes

So even before runtime behavior, this failed the stricter goal of "IsAotCompatible with zero warnings."

NativeAOT runtime failures with official libraries

The official stack published, but real network operations failed under NativeAOT:

Search failure

JsonSerializationException: Unable to find a constructor to use for type NuGet.Protocol.PackageSearchMetadata.
A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.
Path 'data[0].@id', line 1, position 149.

This came from NuGet.Protocol.PackageSearchResourceV3 while processing the nuget.org search response.

Version listing / download failure

FatalProtocolException: Unable to get repository signature information for source
https://api.nuget.org/v3-index/repository-signatures/5.0.0/index.json.

Both version listing and package download hit this under NativeAOT.

CoreCLR comparison

For reference, both stacks worked on CoreCLR:

Stack Search avg Versions avg Download avg
NuGetFetch 461.96 ms 105.75 ms 437.14 ms
NuGet.Protocol 687.71 ms 295.15 ms 408.33 ms

CoreCLR published directory sizes were similar (~88.7 MB vs ~90.1 MB), but the NativeAOT size delta was substantial.

Conclusion

The official libraries are closer than they used to be because they now advertise AOT compatibility, but for dotnet-inspect they are not yet a drop-in replacement for the custom libraries.

At least in this experiment, they miss the target in three ways:

  1. trim/AOT warnings are still present
  2. real remote operations fail under NativeAOT
  3. NativeAOT output size is much larger than the current custom path

Suggested next steps

  • Keep the current custom NuGet helper path for now
  • Revisit after upstream NuGet.Protocol / NuGet.Configuration can pass this scenario with:
    • zero AOT warnings
    • successful search/version/download operations under NativeAOT
  • Optionally distill this into a smaller upstream repro if we want to file against NuGet.Client

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions