Skip to content

Conversation

@baronfel
Copy link
Member

@baronfel baronfel commented Nov 14, 2025

While debugging test failures in CommunityToolkit/Aspire#949 with @aaronpowell we discovered that the Microsoft.SqlServer.DacFx package includes a dependency on Microsoft.Build that seems to be causing MSbuild runtime dlls to end up in the test project!

This is very likely an authoring error that we should track down with them, but what surprised me was that the check for the RuntimeAssets=exclude provided by this library didn't flag this. On further investigation I discovered two gaps in the check:

  • it didn't cover transitive PackageReferences to the MSBuild runtime packages from the project referencing locator
  • it didn't cause the check to happen on projects that depend on the project referencing locator

The Aspire scenario is

  • Package with MSBuild references (Microsoft.SqlServer.DacFx)
  • is referenced by a library package that references Locator (so those MSBuild deps are transitive deps to this project, and not found)
  • which is referenced by a test app project (so the check doesn't run on this project and those transitive deps would not be found even if it did)

To fix this, we need to do two things

  • make the check more correct by checking the runtime assets of the project in question after ResolvePackageReferences
    • this fixes the 'transitive packageref' problem
  • also ship the check as a buildTransitive check
    • this fixes the 'locator is referenced by a lib that is then referenced by an app' problem

Given this lib (dotnet new classlib):

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Locator" Version="1.10.11-ga349ee7ffd" />
    <PackageReference Include="Microsoft.SqlServer.DacFx" Version="170.2.70" />
  </ItemGroup>

</Project>

and this test project referencing the lib (dotnet new mstest):

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MSTest" Version="4.0.1" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\lib\lib.csproj" />
  </ItemGroup>

</Project>

Building the test project does now trigger our error:

> dotnet build -bl
Restore complete (0.3s)
  test net10.0 failed with 1 error(s) (0.0s)
    /Users/chethusk/.nuget/packages/microsoft.build.locator/1.10.11-ga349ee7ffd/buildTransitive/Microsoft.Build.Locator.targets(19,5): error A PackageReference to an MSBuild run-time package without ExcludeAssets="runtime" exists in your project. This will cause MSBuild run-time assemblies to be copied to your output directory, causing your application to load them at runtime. To use the copy of MSBuild registered by MSBuildLocator, set ExcludeAssets="runtime" on the MSBuild PackageReferences. To disable this check, set the property DisableMSBuildAssemblyCopyCheck=true in your project file (not recommended as you must distributed all of MSBuild + associated toolset). Package(s) referenced: Microsoft.Build@15.9.20, Microsoft.Build.Framework@15.9.20, Microsoft.Build.Utilities.Core@15.9.20

While in here, I also added a small README for the package so that the NuGet.org experience is nicer.

@@ -1,25 +1,24 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="EnsureMSBuildAssembliesNotCopied" AfterTargets="Build" Condition="'$(DisableMSBuildAssemblyCopyCheck)' != 'true'">
<Target Name="EnsureMSBuildAssembliesNotCopied" AfterTargets="ResolvePackageAssets" Condition="'$(DisableMSBuildAssemblyCopyCheck)' != 'true'">
Copy link
Member Author

Choose a reason for hiding this comment

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

ResolvePackageAssets creates RuntimeCopyLocalItems for all dependencies, so we have to filter those down to just the ones that came from the MSbuild-related dependencies we want to flag.

<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Engine'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.NET.StringTools'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'NuGet.Frameworks'))" />
<MSBuildAssembliesCopyLocalItems Include="@(_MSBuildAssembliesCopyLocalItems->WithMetadataValue('CopyLocal', 'true')->WithMetadataValue('AssetType', 'runtime'))" />
Copy link
Member Author

Choose a reason for hiding this comment

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

Once we have the candidate set, we can find just the ones that are runtime assets and were copylocal'd.

<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.NET.StringTools'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'NuGet.Frameworks'))" />
<MSBuildAssembliesCopyLocalItems Include="@(_MSBuildAssembliesCopyLocalItems->WithMetadataValue('CopyLocal', 'true')->WithMetadataValue('AssetType', 'runtime'))" />
<_DistinctMSBuildPackagesReferencedPoorly Include="@(MSBuildAssembliesCopyLocalItems->Metadata('NuGetPackageId')->Distinct())" />
Copy link
Member Author

Choose a reason for hiding this comment

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

such assets will have the nuget package id and version metadata on them, so we can distinct the IDs easily and make nice error messages.

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.

4 participants