How can one modify an ItemDefinitionGroup from an MSBuild target?

孤街醉人 提交于 2019-12-12 07:37:56

问题


I have an msbuild script I wrote to compile Google Protocol Buffers files:

<ItemGroup>
  <ProtocolBuffer Include="Whitelist.proto" />
  <ProtocolBuffer Include="Whitelist2.proto" />
</ItemGroup>
<ItemDefinitionGroup>
  <ProtocolBuffer>
    <ProtoPath>$(ProjectDir)</ProtoPath>
  </ProtocolBuffer>
</ItemDefinitionGroup>
<PropertyGroup>
  <ProtoC>$([System.IO.Path]::GetFullPath($(ProjectDir)..\ThirdParty\protobuf-2.4.1\protoc.exe))</ProtoC>
  <ProtoOutPath>$(IntDir)CompiledProtocolBuffers</ProtoOutPath>
</PropertyGroup>
<Target Name="CompileProtocolBuffers"
        BeforeTargets="ClCompile"
        Inputs="@(ProtocolBuffer)"
        Outputs="@(ProtocolBuffer->'$(ProtoOutPath)\%(FileName).pb.cc');@(ProtocolBuffer->'$(ProtoOutPath)\%(FileName).pb.h')">
  <MakeDir Directories="$(ProtoOutPath)" />
  <Exec
    Command="&quot;$(ProtoC)&quot; --proto_path=&quot;$([System.IO.Path]::GetDirectoryName(%(ProtocolBuffer.ProtoPath)))&quot; --cpp_out=&quot;$(ProtoOutPath)&quot; &quot;%(ProtocolBuffer.FullPath)&quot; --error_format=msvs"
        />
  <ItemGroup>
    <ClInclude Include="$(ProtoOutPath)\%(ProtocolBuffer.FileName).pb.h" />
    <ClCompile Include="$(ProtoOutPath)\%(ProtocolBuffer.FileName).pb.cc">
      <AdditionalIncludeDirectories>$(MSBuildThisDirectory)..\ThirdParty\protobuf-2.4.1\src</AdditionalIncludeDirectories>
      <PrecompiledHeader></PrecompiledHeader>
      <DisableSpecificWarnings>4244;4276;4018;4355;4800;4251;4996;4146;4305</DisableSpecificWarnings>
      <PreprocessorDefinitions>GOOGLE_PROTOBUF_NO_RTTI</PreprocessorDefinitions>
      <WarningLevel>Level3</WarningLevel>
    </ClCompile>
  </ItemGroup>
</Target>

This compiles the protocol buffers files perfectly, and adds them to the compiler's inputs (yay!). However, my other source files that want to include the .pb.h files need to know where these files got generated -- that generation location needs to be put on the include path.

Therefore, if and only if the user has included a <ProtocolBuffer item somewhere in their script, I want to add the generation location (in this case $(ProtoOutPath) to ClCompile's <AdditionalIncludeDirectories>.

Is that possible or do I need to make .cpp files that want to use these generated bits jump through hoops?


回答1:


Read your question and thought "can't be that hard". Man, was I wrong. First I thought just putting a condition on it, but of course one can't use ItemGroups in toplevel conditions because of evaluation order. Then I figured it's also not possible to put an ItemDefinitionGroup in a target (cause there one can use conditions) and modify it there. Then I bonked my head on the keyboard a couple of times after I realized that's probably why you asked the question :] (btw you know including a nonexisting directory is not really a problem since the compiler will happily ignore it?)

Maybe there's a simpler solution, but lastly I figured: if nothing works, my favourite msbuild toy aka CodeTaskFactory must be able to fix it. It does (I hope, didn't fully test the result), but it's not straightforward at all. Here you go, make sure to invoke the Test target somewhere before the C++ build starts.

<!--Uncomment the below to define some ProtocolBuffers-->  
<!--<ItemGroup>
  <ProtocolBuffer Include="Whitelist.proto" />
  <ProtocolBuffer Include="Whitelist2.proto" />
</ItemGroup>-->

<!--Suppose these are your default include files defined in your C++ project-->
<ItemDefinitionGroup Label="DefaultIncludes">
  <ClCompile>
    <AdditionalIncludeDirectories>/path/to/x;/path/to/y</AdditionalIncludeDirectories>
  </ClCompile>
</ItemDefinitionGroup>

<!--Include at least one item so we can play with it-->
<ItemGroup>
  <ClCompile Include="iamaninclude"/>
</ItemGroup>

<!--Use code to append to AdditionalIncludeDirectories-->
<UsingTask TaskName="AppendMetadata" TaskFactory="CodeTaskFactory" 
           AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
  <ParameterGroup>
    <Append ParameterType="System.String" Required="true"/>
    <ItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true"/>
    <OutputItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
  </ParameterGroup>
    <Task>
        <Code>
        <![CDATA[
            const string dirz = "AdditionalIncludeDirectories";
            foreach( var item in ItemList )
            {
              var cur = item.GetMetadata( dirz );
              item.SetMetadata( dirz, cur + ";" + Append );
            }
            OutputItemList = ItemList;
        ]]>
    </Code>
  </Task>
</UsingTask>

<!--Main target-->  
<Target Name="Test">
  <!--stage 1: copy the itemgroup, then clear it:
  if an Output TaskParameter is an Itemgroup, apparently the content
  gets appended to the group instead of replacing it.
  Found no documentation about this whatsoever though???-->
  <ItemGroup Condition="@(ProtocolBuffer) != ''">
    <ClCompileCopy Include="@(ClCompile)"/>
    <ClCompile Remove="@(ClCompile)"/>
  </ItemGroup>

  <!--stage 2: append 'ProtoBufIncludeDir' to AdditionalIncludeDirectories,
  and append the result to the origiginal again-->
  <AppendMetadata ItemList="@(ClCompileCopy)" Append="ProtoBufIncludeDir" Condition="@(ProtocolBuffer) != ''">
    <Output ItemName="ClCompile" TaskParameter="OutputItemList"/>
  </AppendMetadata>

  <!--stage 3: use modified itemgroup-->
  <Message Text="@(ClCompile->'%(Identity): %(AdditionalIncludeDirectories)')"/>
</Target>

This prints

iamaninclude: /path/to/x;/path/to/y

unless the ProtocolBuffer is not empty in which case it prints

iamaninclude: /path/to/x;/path/to/y;ProtoBufIncludeDir


来源:https://stackoverflow.com/questions/18304911/how-can-one-modify-an-itemdefinitiongroup-from-an-msbuild-target

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!