如何在MSBuild目标中修改ItemDefinitionGroup?

6

我有一个msbuild脚本,用于编译Google Protocol Buffers文件:

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

这样可以完美地编译协议缓冲文件,并将它们添加到编译器的输入中(太棒了!)。但是,我的其他源文件要包含 .pb.h 文件,则需要知道这些文件的生成位置--需要将该生成位置放在包含路径上。
因此,仅当用户在其脚本中的某处包含了 <ProtocolBuffer 项时,我才希望将生成位置(在这种情况下为 $(ProtoOutPath))添加到 ClCompile 的 <AdditionalIncludeDirectories> 中。
这是否可能?还是说我需要让想使用这些生成位的 .cpp 文件跳过一些障碍?
1个回答

6
阅读了您的问题并认为“不可能那么难”。但我错了。起初,我只是想在其中加入条件,但由于评估顺序的原因,无法在顶层条件中使用ItemGroups。然后我想,在目标中放置一个ItemDefinitionGroup并在那里进行修改也不可能(因为在那里可以使用条件)。之后,当我意识到这可能就是您提出问题的原因时,我撞了几次键盘(顺便说一下,您知道包含不存在的目录并不是真正的问题,因为编译器会快乐地忽略它吗?)
也许有更简单的解决方案,但最后我想到:如果没有任何方法可行,我的最爱msbuild玩具——CodeTaskFactory必须能够解决它。它确实做到了(我希望如此,但并没有完全测试结果),但这一点都不直接。请看下面的内容,并确保在C++构建开始之前在某个地方调用Test目标。
<!--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>

这将打印

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

除非 ProtocolBuffer 为空,否则它会打印出来。
iamaninclude: /path/to/x;/path/to/y;ProtoBufIncludeDir

1
我遇到了同样的问题(尽管是通过编程方式设置PreprocessorDefinitions而不是AdditionalIncludeDirectories)。这太神奇了,但它的效果出奇的好。谢谢! - Edward Thomson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接