Include additional files in build using MSBuild ProjectReference?

5.1k Views Asked by At

I have a class library that has several files that it depends on, and those files must be packaged up for deployment with any project that depends on this library. Right now this means that I must customize each one of those dependent projects to ensure that they copy the files, in addition to adding the library as an MSBuild PrjectReference. When more files are added, all of the projects must be updated.

I've been looking through Microsoft.Common.targets for a way to include these files with the outputs of the library's own project file, so any project that has a ProjectReference to the library will automatically get the files when doing a build. I haven't gotten anything working yet, but I'm curious more generally if this is possible. It seems like it should be, and the _CopyFilesMarkedCopyLocal target even respects an otherwise-unused %(DestinationSubDirectory) metadata item that would allow for customized placement of those files, which would be perfect.

I believe what I'm missing is, for building a project A that depends on project B, the piece that adds the project outputs of project B into the items for project A's build.

EDIT: Leo's comment, I hadn't noticed that files marked with CopyToOutputDirectory are also copied to dependent project output directories because we use ItemGroups with names other than Content, EmbeddedResource, None, etc. Digging deeper, the target that uses those is GetCopyToOutputDirectoryItems and it appears to recursively call the MSBuild task to determine the project outputs, so I should be able to define some custom target that can be imported into our projects that adds our custom ItemGroups in the GetCopyToOutputDirectoryItems target, so that we don't have to use Content/None, etc.

However, the target that does the copying though is _CopyOutOfDateSourceItemsToOutputDirectoryAlways, which doesn't respect %(DestinationSubDirectory) unfortunately, and so all of these files are copied directly to $(OutDir).

My new goal is to see if there's some way to add custom files into the ReferenceCopyLocalPaths ItemGroup of dependent projects so that they are copied instead by the _CopyFilesMarkedCopyLocal target, which does utilize %(DestinationSubDirectory).

1

There are 1 best solutions below

2
On

Adds custom build action for ItemGroups in the _CopyOutOfDateSourceItemsToOutputDirectoryAlways target

The above title should be closer to bwerks's goal. As test we could to know that the custom build action for ItemGroups would not copied to the Output directory, so we need to our custom build action to the target _CopyOutOfDateSourceItemsToOutputDirectoryAlways.

To accomplish this, open the file Microsoft.Common.CurrentVersion.targets in the MSBuild 15.0 folder C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(Make sure you have sufficient permissions and back up it), find the target _CopyOutOfDateSourceItemsToOutputDirectoryAlways:

<Copy
    SourceFiles = "@(_SourceItemsToCopyToOutputDirectoryAlways)"
    DestinationFiles = "@(_SourceItemsToCopyToOutputDirectoryAlways->'$(OutDir)%(TargetPath)')"
    ...
        >

  <Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>

</Copy>

Then we could to know the copy source file is @(_SourceItemsToCopyToOutputDirectoryAlways), search _SourceItemsToCopyToOutputDirectoryAlways this in the targets, you will find:

<ItemGroup>
  <_SourceItemsToCopyToOutputDirectoryAlways KeepMetadata="$(_GCTODIKeepMetadata)" Include="@(ContentWithTargetPath->'%(FullPath)')" Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
  <_SourceItemsToCopyToOutputDirectory       KeepMetadata="$(_GCTODIKeepMetadata)" Include="@(ContentWithTargetPath->'%(FullPath)')" Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</ItemGroup>

Could to know the source file is @(ContentWithTargetPath), keeping search the ContentWithTargetPath in the target, finally we got following:

<AssignTargetPath Files="@(Content)" RootFolder="$(MSBuildProjectDirectory)">
  <Output TaskParameter="AssignedFiles" ItemName="ContentWithTargetPath" />
</AssignTargetPath>

So, we could to know how the target is to copy the default build action file to the output directory.

Now, go to our custom build action, we just need add our custom build action to the ItemName="ContentWithTargetPath", so add following in the file Microsoft.Common.CurrentVersion.targets:

<AssignTargetPath Files="@(MyBuildAction)" RootFolder="$(MSBuildProjectDirectory)">
  <Output TaskParameter="AssignedFiles" ItemName="ContentWithTargetPath" />
</AssignTargetPath>

Save it.

For the file in the project file .csproj:

  <ItemGroup>
    <MyBuildAction Include="TextFile1.txt">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </MyBuildAction>
  </ItemGroup>

enter image description here

Hope this helps.