Reputation: 3372
Starting with a .csproj which defines various xml Content files. Have code generation Target which takes some xml files (Target Inputs) and generate .cs files whose names are determined by transformation from the xml files (Target Outputs). In order for MSBuild to determine whether the code building Target needs to run, it needs to inspect the Target Inputs and Outputs. Therefore I am assuming that those Target Inputs and Outputs must be global.
If that's incorrect, there should be another question about how to create a Target who's Outputs are based on Dynamic Items; tried it but the Target keeps being called.
If it's correct, then how to filter the Content at the global level ? Specifically, I want to filter Content Items in the project so that only the one's in a specific directory are used. The Content Items will be added by other developers via the IDE.
This can be achieved using a Target which creates Dynamic Items, doing the filtering in the Condition attribute. That requires Target Batching, which isn't available globally. Using MSBuild 3.5 and Visual Studio 2008.
<?xml version="1.0" encoding="utf-8"?>
<Project
DefaultTargets="Show" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Content Include="badxml\somebadxml1.xml" />
<!-- Note xml\somexml2.xml exists on disk, it just isn't used in this project. -->
<Content Include="xml\somexml1.xml" />
<Content Include="xml\somexml3.xml" />
</ItemGroup>
<!-- Foo should only be defined for Content Items in the "xml" directory. -->
<ItemGroup>
<Foo Include="@(Content->'%(Filename)')"/>
<!-- The line below doesn't work -->
<!-- TestFilter.proj(10,10): error MSB4090: Found an unexpected character '%' at position 3 in condition " '%(Content.RelativeDir)'=='xml' ". -->
<!-- <Foo Condition=" '%(Content.RelativeDir)'=='xml' " Include="@(Content->'%(Filename)')"/> -->
</ItemGroup>
<Target Name="ShowContent">
<Message Text="Content: %(Content.Identity)" />
<Message Text="Content RelDir: %(Content.RelativeDir)" />
</Target>
<Target Name="ShowFoo">
<Message Text="Foo: %(Foo.Identity)" />
</Target>
<Target Name="Show">
<CallTarget Targets="ShowContent;ShowFoo" />
</Target>
</Project>
Why doesn't MSBuild ItemGroup conditional work in a global scope addresses the same issue but from the perspective of asking why this doesn't work, rather than looking for alternative approaches.
Filtering Item's Metadata in msbuild uses Dynamic Items in a Target, and a dummy Output name.
My best guess is that this can't be done without using Dynamic Items in a Target, and the workaround will be rather than using Items which require a Condition, to write out a file with a predefined name and use that as Output placeholder.
Upvotes: 1
Views: 989
Reputation: 3372
So it turns out my assumption was incorrect. It's perfectly acceptable to have Target Outputs which are based on dynamic items. It helps to remember that Targets are batched according to the Outputs definition.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="TestBatch"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Static Item declaration. -->
<ItemGroup>
<Bar Include="Static01">
<Data>Static01 Data</Data>
</Bar>
</ItemGroup>
<Target Name="PreBatchTarget">
<!-- Dynamic Item addition. -->
<ItemGroup>
<Bar Include="Dynamic01">
<Data>Dynamic01 Data</Data>
</Bar>
</ItemGroup>
</Target>
<Target Name="TestBatchTarget"
Outputs="%(Bar.Data)"
>
<Message Text="TestBatchTarget call" />
<Message Text="@(Bar)" />
</Target>
<Target Name="TestBatch"
DependsOnTargets="PreBatchTarget;TestBatchTarget"
>
</Target>
</Project>
msbuild /nologo DynamicTargetOutput.proj
Project "DynamicTargetOutput.proj" on node 0 (default targets).
TestBatchTarget call
Static01
TestBatchTarget:
TestBatchTarget call
Dynamic01
Done Building Project "DynamicTargetOutput.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.09
Upvotes: 1