dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI
https://dot.net/core
MIT License
2.67k stars 1.06k forks source link

Add Support for explicitly including Dependencies in build output in PackageReference #32863

Open RickStrahl opened 1 year ago

RickStrahl commented 1 year ago

Is your feature request related to a problem? Please describe.

Yes - use case is creating an addin where output is generated into a very specific output folder with very specific files. Currently in Core (as opposed to NetFX) build output does not include dependencies and there appears to be no way to force an individual package to explicitly include dependencies in the build output.

There are options to explicitly remove dependencies in scenarios when using NetFX or when explicitly setting <CopyLocalLockFileAssemblies>.

Describe the solution you'd like

Specifically, I would like to see the following behavior:

<PackageReference Include="Westwind.Utilities" Version="4.0.1" 
                   IncludeAssets="all"  
                   PrivateAssets="none" />

to output its assembly into the build folder. I'd imagine IncludeAssets=compile;runtime and PrivateAssets=contentfiles;analyzers should also work but same deal.

IOW there are all these options to control how assets are not output into the build folder, but there are no options currently to explicitly force them into the build folder. Or rather it looks like there are options (see expectations) but they don't work as expected.

Context

For reference my exact scenario is the following:

Here's a project file that demonstrates:

Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <Version>2.9</Version>
    <TargetFrameworks>net70-windows;net472</TargetFrameworks>

    <AssemblyName>WeblogAddin</AssemblyName>
    <UseWPF>true</UseWPF>

    <OutputPath>$(SolutionDir)MarkdownMonster/bin/$(Configuration)/$(TargetFramework)/Addins/Weblog</OutputPath>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>

    <!-- this works as a workaround  but it's global - I'm asking for per package behavior- -->
    <!--<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>-->
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MahApps.Metro" Version="2.4.9">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    ...
    <PackageReference Include="Westwind.Utilities" Version="4.0.0" />
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>

    <!-- this dependency is not outputting into the build folder -->
    <PackageReference Include="Kveer.XmlRPC" Version="1.2.2" 
                      IncludeAssets="all"  
                      PrivateAssets="none" />

    <ProjectReference Include="../../MarkdownMonster/MarkdownMonster.csproj">
      <Private>false</Private>
      <IncludeAssets>compile</IncludeAssets>
    </ProjectReference>
  </ItemGroup>
</

Other

There's a workaround that more or less works now which is to use:

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

at the top level. While this works this also forces all assemblies to be pushed into the output folder. In my case of an Addin I don't want all the base references that are already used in the main project to be put into the output folder, so I have to hide them explicitly.

Also, this behavior is different than NetFX (and I think earlier versions of Core pre-5.0?) which does output dependencies into the build output folder.

Setup

baronfel commented 1 year ago

A couple thoughts for triage/follow-up:

Overall, Rick is making a plugin architecture, and there are some somewhat well-formed recommendations we can make now (using MSBuild as another example here):

Should these (and other modifications) be rolled up into a new kind of OutputType, or something roughly equivalent to that concept?

RickStrahl commented 1 year ago

Correct, this is for build.

It seems to me includeAssets=build is the one that would produce the expected dependency inclusive output I expect?

As to using Package?

In my use case the addins are developed side by side with the main application and they need to go into a specific folder that I specify. Setting up an extra publish step changes that entire flow that works now and would basically require custom publish operation plus some sort of copy most likely. Not happening - In that case I would use CopyLocalLockFileAssemblies.

And as said earlier: The work around using CopyLocalLockFileAssemblies works, but I think it makes for terrible usability and discoverability (and that horrible naming fail 😂). The logical place where I would expect this to be overridden is at the package level.

And just to be clear it looks like PrivateAssets="none" does not work. If any of those options worked that would be fine but they do not currently.

All of this stuff is confusing as hell. I found some of the docs on this and they are clear as mud. Not so much because they are badly written but because the names don't accurately describe what the features do or rather the names use overloaded terms that can be interpreted in directly opposing ways unless you're intimately familiar with the build nomenclature. It also doesn't help that the way the names are structured they suggest that explicit build output should work. (ie. IncludeAssets=compile;runtime;build)

I have 3 different blog posts on topics related to build output management and will now have a 4th, all with similar but different issues around this very same issue of output generation. Tons of StackOverflow issues around this stuff as well - lots of pain in this area.

baronfel commented 1 year ago

That's a lot to dig into, and I will do so, but I wanted to clarify quickly that IncludeAssets=build has nothing to do with the 'build output's as you're thinking of it - instead it instructs NuGet to load any props/targets files from packages during the build.

The canonical reference for all of these tags on PackageReference is here, if that helps explain in more detail.

Yes, I completely agree that the names here are often the opposite of helpful - they're the kind of names that make technical sense if you're deep into the domain, but not as much if you're coming to it with fresh eyes (as most developers do!).