Search code examples
c#msbuilddotnet-sdk

Is ContentWithTargetPath actually a valid MSBuild Item?


TL;DR

Is ContentWithTargetPath actually just the internal MSBuild representation of <Content ..><TargetPath>.. or is this really a "proper" MSBuild configuration item?


Only one (1!) of several 100s of our projects contains an entry

<ContentWithTargetPath Include="..\x-model\v6\stuff.json"> 
  <Link>data\stuff.json</Link>
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  <TargetPath>stuff.json</TargetPath>
</ContentWithTargetPath>

I tried to find any docs for this, but while it is mentioned many times on S.O. here, there does not appear to be any documentation for this MSBuild Item.

What I further found was a mention on one github issue https://github.com/dotnet/msbuild/issues/2795 where ContentWithTargetPath is mentioned together with _NoneWithTargetPath.

I also found the Common MSBuild project items reference where the TargetPath optional sub-entry is listed for <Content ...> (but not for <None ...>, even though None also seems to support it).

Also, as far as I can tell, <TargetPath>in conjunction with CopyToOutputDirectory behaves exactly the same, regardless of whether I use None, Content, or ContentWithTargetPath as element in my actual csproj file.

Can anyone shed some light on why I can find over 50 entries of this MSBuild property here on S.O. but nowhere documented on the MS docs for MSBuild or even prominently in any dotnet github issues? Is there any history to this configuration item?


Solution

  • TL;DR Use Content in your project files, use ContentWithTargetPath when you need to run custom build logic as part of your build that executes after the AssignTargetPaths target and you missed your chance of your Content items being picked up.


    Unfortunately, a lot of parts in the build process are not fully documented and can only be understood by looking at the actual "code" in MSBuild / the .NET SDK that ships the necessary logic.

    As for ContentWithTargetPath, it is not prefixed with an underscore and thus you can expect that people will try not to make breaking changes to code that creates or uses it.

    Internally, ContentWithTargetPath items get calculated during the build process since Content does not need to contain a TargetPath or Link metadata by default so there is a step that creates an intermediate item that later steps in the build process can rely on - so no other logic (copy-to-output / publish) needs to implement calculating a target path themselves and there is a shared logic to rely on the TargetPath metdata existing on these ContentWithTargetPath items.

    There is not much use in documenting ContentWithTargetPath since you can achieve most things with the Content item when authoring your projects and usually only advanced build customizations need to understand the more internal parts of the build process (for which you'd have to already read the built-in logic anyway). Also this means when people only use Content tooling (e.g. different IDEs) needs to focus on understanding fewer items and not try to be smart about when to use which.

    There is a small caveat in that after the ContentWithTargetPath items are created from Content items, build logic running after that step (AssignTargetPaths target) have to create ContentWithTargetPath items if they want to contribute items that need to be copied/published. This can be the case if you want to hook logic into dotnet publish that adds additional items to the publish output but which does not need to run on dotnet build for example.