C
C#3d ago
peppy

MSBuild: Excluding objects if %(Filename) matches any in ItemGroup

Given a target:
<Target Name="ExplicitRemoveFromFilesToBundle" BeforeTargets="GenerateSingleFileBundle" DependsOnTargets="PrepareForBundle">
<ItemGroup>
<FilesToRemoveFromBundle Include="@(FilesToBundle)" Condition="$([System.String]::new('%(Filename)').Contains('TerraFX.Interop.Windows')) AND ('%(Extension)' == '.dll')" />
</ItemGroup>
<ItemGroup>
<FilesToBundle Remove="@(FilesToRemoveFromBundle)" />
</ItemGroup>
</Target>
<Target Name="ExplicitRemoveFromFilesToBundle" BeforeTargets="GenerateSingleFileBundle" DependsOnTargets="PrepareForBundle">
<ItemGroup>
<FilesToRemoveFromBundle Include="@(FilesToBundle)" Condition="$([System.String]::new('%(Filename)').Contains('TerraFX.Interop.Windows')) AND ('%(Extension)' == '.dll')" />
</ItemGroup>
<ItemGroup>
<FilesToBundle Remove="@(FilesToRemoveFromBundle)" />
</ItemGroup>
</Target>
that receives FilesAsBundle as the following input (abridged for simplicity):
'C:\Users\f\.nuget\packages\terrafx.interop.windows\10.0.26100.2\lib\net9.0\TerraFX.Interop.Windows.dll;C:\Users\f\.nuget\packages\hexa.net.imgui\2.2.8.4\runtimes\win-x86\native\cimgui.dll;D:\fh\artifacts\obj\Fahrenheit.Tools.ModManager\Release\net9.0\win-x86\fhmodmgr.deps.json'
'C:\Users\f\.nuget\packages\terrafx.interop.windows\10.0.26100.2\lib\net9.0\TerraFX.Interop.Windows.dll;C:\Users\f\.nuget\packages\hexa.net.imgui\2.2.8.4\runtimes\win-x86\native\cimgui.dll;D:\fh\artifacts\obj\Fahrenheit.Tools.ModManager\Release\net9.0\win-x86\fhmodmgr.deps.json'
I can remove TerraFX.Interop.Windows.dll from the list. But I have a need to match any of several filenames. I naively define a ItemGroup of DepsToExclude as such:
'TerraFX.Interop.Windows;Hexa.NET.DirectXTex;Hexa.NET.ImGui.Backends;Hexa.NET.ImGui;HexaGen.Runtime.COM;HexaGen.Runtime;ImGuiImpl;cimgui;DirectXTex'
'TerraFX.Interop.Windows;Hexa.NET.DirectXTex;Hexa.NET.ImGui.Backends;Hexa.NET.ImGui;HexaGen.Runtime.COM;HexaGen.Runtime;ImGuiImpl;cimgui;DirectXTex'
and then attempted to amend the comparison:
<ItemGroup>
<FilesToRemoveFromBundle Include="@(FilesToBundle)" Condition="$([System.String]::Copy('%(Filename)').Contains('@(DepsToExclude)')) AND ('%(Extension)' == '.dll')" />
</ItemGroup>
<ItemGroup>
<FilesToRemoveFromBundle Include="@(FilesToBundle)" Condition="$([System.String]::Copy('%(Filename)').Contains('@(DepsToExclude)')) AND ('%(Extension)' == '.dll')" />
</ItemGroup>
and also attempted to directly Remove from FilesToBundle as follows:
<ItemGroup>
<FilesToBundle Remove="**/*/TerraFX.Interop.Windows.dll" />
<!-- others... -->
</ItemGroup>
<ItemGroup>
<FilesToBundle Remove="**/*/TerraFX.Interop.Windows.dll" />
<!-- others... -->
</ItemGroup>
but neither approach was successful. I'm struggling to follow how and when MSBuild expands @ to automatically cover all elements in a group. What would be the correct way to express this?
1 Reply
peppy
peppyOP3d ago
That version looked as follows:
<Message Text="FilesToBundle '@(FilesToBundle)'" Importance="High" />

<ItemGroup>
<FilesToBundle Remove="**/*/TerraFX.Interop.Windows.dll" />
<FilesToBundle Remove="**/*/Hexa.NET.DirectXTex.dll" />
<FilesToBundle Remove="**/*/Hexa.NET.ImGui.Backends.dll" />
<FilesToBundle Remove="**/*/Hexa.NET.ImGui.dll" />
<FilesToBundle Remove="**/*/HexaGen.Runtime.COM.dll" />
<FilesToBundle Remove="**/*/HexaGen.Runtime.dll" />
<FilesToBundle Remove="**/*/ImGuiImpl.dll" />
<FilesToBundle Remove="**/*/cimgui.dll" />
<FilesToBundle Remove="**/*/DirectXTex.dll" />
</ItemGroup>

<Message Text="FilesToBundle '@(FilesToBundle)'" Importance="High" />
<Message Text="FilesToBundle '@(FilesToBundle)'" Importance="High" />

<ItemGroup>
<FilesToBundle Remove="**/*/TerraFX.Interop.Windows.dll" />
<FilesToBundle Remove="**/*/Hexa.NET.DirectXTex.dll" />
<FilesToBundle Remove="**/*/Hexa.NET.ImGui.Backends.dll" />
<FilesToBundle Remove="**/*/Hexa.NET.ImGui.dll" />
<FilesToBundle Remove="**/*/HexaGen.Runtime.COM.dll" />
<FilesToBundle Remove="**/*/HexaGen.Runtime.dll" />
<FilesToBundle Remove="**/*/ImGuiImpl.dll" />
<FilesToBundle Remove="**/*/cimgui.dll" />
<FilesToBundle Remove="**/*/DirectXTex.dll" />
</ItemGroup>

<Message Text="FilesToBundle '@(FilesToBundle)'" Importance="High" />
FilesToBundle contains all entries at the beginning, but the Removes have no effect that is inconvenient, because rooting it (with a literal) means I have to presuppose where a user might have their NuGet package cache at some point I realized that I can refer to entries in an ItemGroup of <DepsToExclude Include="TerraFX.Interop.Windows" /> with %(DepsToExclude.Include), but that performs a comparison between entry N in FilesToBundle and DepsToExclude respectively when what I'd like is to compare it against all DepsToExclude Oh there's just %(RootDir) according to https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata?view=vs-2022 if that helps me in any way, which I'm not certain it does that is unfortunately not the case even on my dev machine, and God only knows about someone else's surprisingly, that has no effect, I'm trying to step through why oh, Filename is without extension right Somehow removing just those four characters (\.dll) causes:
D:\fh\Directory.Build.targets(51,9): error MSB4036: The "FilesToBundle" task was not found.
D:\fh\Directory.Build.targets(51,9): error MSB4036: The "FilesToBundle" task was not found.
I'm confused by what it even means by this Right Good thinking right. well, the regex works- once I expand it to cover every DLL in that list; I suppose that's a win it is a bit more unwieldy than I originally expected it to be, but I'll take whatever I can get at this point thank you very much does the regex i.e. the condition have to be on a single line? out of curiosity honestly, just splitting up the condition across lines and having the regex be a single line all its own was enough thanks, this was a lifesaving assist in the course of trying one relatively simple thing I ended up learning a ton about MSBuild and .NET packaging

Did you find this page helpful?