Skip to content

Proposal: implement a SdkResolver for NuGet packages #5220

@natemcmaster

Description

@natemcmaster

MSBuild 15.3 includes an new API, Microsoft.Build.Framework.SdkResolver. (see dotnet/msbuild#2002). In theory, we could implement a NuGetPackageSdkResolver that resolves the "Sdk" property to files in the NuGet cache.

Goal
Distribute versioned MSBuild tasks, targets, and other tooling as a NuGet package.

Usage

<Project>
   <Sdk Name="Microsoft.NET.Sdk" />
   <Sdk Name="Microsoft.NET.Test.Sdk" MinimumVersion="15.0.0" />
   <Sdk Name="my.team.shared.buildsettings" Version="1.0.0-*" />
</Project>

In this example, Microsoft.Net.Sdk is not a nuget package, so the resolver would no-op.
On the other hand, Microsoft.NET.Test.Sdk and my.team.shared.buildsettings are nuget packages that contains MSbuild targets and tasks. The resolver could download these packages to make them available to MSBuild.

Layout of an "SDK" package

(pkg root)/
  - build/
    - Sdk/
      + Sdk.props
      + Sdk.targets

What the resolver does
(pseudocode)

class NuGetSdkResolver : Microsoft.Build.Framework.SdkResolver
{
    public override SdkResult Resolve(SdkReference sdk, SdkResolverContext context, SdkResultFactory factory)
    {
        var resolvedVersion = ChoosePackageVersionOrDefault(sdk.Version ?? sdk.MinimumVersion);
        RestorePackageIfNotFound(sdk.Name, resolvedVersion);
        var sdkPath = Path.Combine("%NUGET_PACKAGES%", sdk.Name, resolvedVersion, "build/Sdk");

        return factory.IndicateSuccess(sdkPath, resolvedVersion);
    }
}

Why not just use a PackageReference?
Many simple scenarios are already solved by adding build assets to a package, however, MSBuild tooling from an NuGet package still has some limitations:

  • Build requires two msbuild invocations. msbuild /t:Restore && msbuild /t:Build. Just running msbuild /t:Restore;Build causes issues because nuget.g.targets and UsingTasks are not resolved soon enough.
  • You have to express TargetFramework to make PackageReference work. This property only makes sense for csproj, but doesn't have meaning for MSBuild scripts that do other build automation.
  • You cannot have a PackageReference that conditionally adds more PackageReferences or DotNetCliToolReference.
  • Users have to know to set PrivateAssets=All to avoid having tooling packages end up in the generated nuspec.
  • You have to import Microsoft.Common.targets (and everything it brings with it) in order to use PackageReference.

One possibility
If this were implemented, I could easily create a package that looks like this:

Microsoft.AspNetCore.LTS.Lineup.nupkg/
- build
  - Sdk/
     Sdk.targets
     Tasks.dll
<!-- Sdk.targets -->
<Project>
  <UsingTask AssemblyFile="Tasks.dll" TaskName="CustomAspNet.Build.Task" />
  <ItemGroup>
     <!-- Update package references to the preferred lineup version if they are included in the project.-->
     <PackageReference Update="Kestrel" Version="1.0.4" IsImplicitlyDefined="true" />
     <PackageReference Update="Mvc" Version="1.0.5" IsImplicitlyDefined="true"  />
     <!-- add a CLI tool -->
     <DotNetCliToolReference Include="dotnet-watch" Version="1.0.1" />
   </ItemGroup>
</Project>

This could solve the "Lineups" feature. #2572

cc @emgarten @AndyGerlicher @AArnott @nguerrera

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions