2

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).

bwerks
  • 8,651
  • 14
  • 68
  • 100
  • Don't have time to check this now but can't you trace back from _CopyFilesMarkedCopyLocal the target responsible for listing the files to copy? I'm fairly sure there's a way to hook into that to get arbitrary files copied from referenced projects. – stijn Aug 26 '18 at 07:54
  • 1
    Set the properties **Copy to Output Directory** for those additional files is not works for you? If you set that property to **Copy Always**, MSBuild will copy it to the output directory and any project that has a ProjectReference to the library will automatically get the files in the output folder when doing a build. – Leo Liu Aug 27 '18 at 06:51
  • Interesting! I've edited the OP with new information. – bwerks Aug 27 '18 at 21:05
  • @bwerks, Any update for this issue? Have you resolved this issue? If not, would you please let me know the latest information about this issue? – Leo Liu Aug 29 '18 at 03:08

1 Answers1

1

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.

Leo Liu
  • 71,098
  • 10
  • 114
  • 135
  • 1
    This is a useful look at extending Microsoft.Common.targets. Right now I'm attempting to write a target that uses this approach without modifying Microsoft.Common.targets, since we can't source control Microsoft.Common.targets itself and will change with later (and earlier) VS versions. – bwerks Aug 29 '18 at 18:41
  • Also, since this approach injects custom files into ItemGroups that are copied by _CopyOutOfDateSourceItemsToOutputDirectoryAlways, there is no way to use DestinationSubDirectory since that target does not acknowledge it in the copy statement. It seems like I would need to inject my files into the ReferenceCopyLocalPaths ItemGroup, but this ItemGroup is set by ResolveAssemblyReference task, and the documentation for it doesn't have much detail. – bwerks Aug 29 '18 at 18:51