Item to copy to output
KirillOsenkov opened this issue · comments
If we need to copy a file to output directory, the current pattern seems to be:
<ItemGroup>
<None Include="myfile.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
It works by first assigning the target path in the AssignTargetPaths
(plural!) target. The output of the AssignTargetPath
task (singular!) is copied to the NoneWithTargetPath
item:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 3197 to 3199 in dbf8d12
All the AssignTargetPath
task does is append the TargetPath
metadata to the item, usually just the name. It is the relative path inside the project output directory where to copy the file. Without it, you'll get an error saying the destination file path is a directory (because the target file path is empty).
msbuild/src/Tasks/AssignTargetPath.cs
Line 122 in dbf8d12
Then NoneWithTargetPath
gets copied to _ThisProjectItemsToCopyToOutputDirectory
:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 5176 to 5179 in dbf8d12
Then the target returns and the items go into _ThisProjectItemsToCopyToOutputDirectory
:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 5195 to 5197 in dbf8d12
Then the items flow into _SourceItemsToCopyToOutputDirectory
:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 5204 to 5214 in dbf8d12
Finally the copy happens in _CopyOutOfDateSourceItemsToOutputDirectory
:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 5264 to 5282 in dbf8d12
Note the destination is $(OutDir)%(TargetPath)
. This is what the TargetPath
metadata was needed for.
======
Now, the problem with the None
item is that it's considered an input by the Visual Studio Fast Up-To-Date Check. So if you are generating an item as part of the project build, and then add it to the None
item to ensure it gets copied, you have a situation where the project's output is also its input, so the FUTDC will always consider the project not up-to-date, because the generated file was written to after the primary output assembly, but it's now an input, so we have an input newer than output.
I was looking for a loophole to find a better way to do this. I first tried to directly add to the _ThisProjectItemsToCopyToOutputDirectory
item, but without the TargetPath
metadata I got an error from the copy task because the destination file name was empty.
The only thing I found that works is instead of None
to add it to _CompileItemsToCopy
:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 5163 to 5174 in dbf8d12
Conveniently, AssignTargetPath
is being called for this item, so it acquires the TargetPath
metadata.
However, obviously, it's a hack.
I wonder what's the official blessed pattern for ensuring that a file generated by this project gets copied to output. If we don't have one, we should make one and make it easy and fool-proof.