使用MSBudio和VisualStudio 2012 +预处理C++文件

使用MSBudio和VisualStudio 2012 +预处理C++文件,c++,visual-studio,msbuild,C++,Visual Studio,Msbuild,我有一系列标准cpp文件,每个文件都包含一个特定于文件的include语句。但是,在调用标准C++编译器之前,必须包含一个预处理工具来填充这些文件。p> 棘手的是,我希望使用MSBuild将其完全集成到VisualStudio中。因此,当我在CPP文件上打开VisualStudio的属性窗口时,我希望看到所有标准的C++编译器选项,并且理想地,一些控制预处理器工具的自定义属性。作为面向对象的类比,我希望我的构建工具继承标准CL-MSBuild规则的所有内容,并向其中添加一些自定义属性和构建步骤

我有一系列标准cpp文件,每个文件都包含一个特定于文件的include语句。但是,在调用标准C++编译器之前,必须包含一个预处理工具来填充这些文件。p> 棘手的是,我希望使用MSBuild将其完全集成到VisualStudio中。因此,当我在CPP文件上打开VisualStudio的属性窗口时,我希望看到所有标准的C++编译器选项,并且理想地,一些控制预处理器工具的自定义属性。作为面向对象的类比,我希望我的构建工具继承标准CL-MSBuild规则的所有内容,并向其中添加一些自定义属性和构建步骤

我已经成功地通过一个非常辛苦的过程来完成,基本上创建了一个自定义MSBug规则,并将大多数C++选项复制/粘贴到我的自定义规则中。最后,我通过MsBudio.PrpS文件中的命令行文件条目,将百万个C++选项传递给标准C++编译器。这是非常复杂的,当我更新VisualStudio.</P>时,C++选项不会自动更新。


我可以找到很多自定义MSBuild规则的示例,但是我还没有找到一个可以与现有规则相结合的示例。

我认为MSBuild不太受欢迎

无论如何,经过多年的反复思考,我终于找到了一些东西,就在我发布我的问题后不久。关键是寻找扩展现有规则的方法,显然,我以前从未尝试过

通常,在VS中创建生成自定义项时,最终会生成3个文件:

MyCustomBuild.xml: 包含属性和开关,如VS的属性表所示

MyCustomBuild.props: 包含这些属性的默认值。可以通过使用Condition属性使它们成为有条件的

MyCustomBuild.targeters: 包含用于加载xml和目标/任务项的行

因此,第一部分是扩展现有的C/C++属性,如VisualStudio中所示。我找到了这个链接,它最终给了我一些可以使用的东西:

这里是xml位

<Rule
  Name="RuleToExend"
  DisplayName="File Properties"
  PageTemplate="generic"
  Description="File Properties"
  OverrideMode="Extend"
  xmlns="http://schemas.microsoft.com/build/2009/properties">
  <!-- Add new properties, data source, categories, etc -->
</Rule>
名称属性: Name属性必须与正在扩展的规则匹配。在本例中,我想扩展CL规则,所以我将该属性设置为=CL

DisplayName属性: 这是可选的。提供时,它将覆盖属性页上显示的工具名称。在本例中,显示的工具名称为C/C++。通过设置此属性,我可以将其更改为显示我的C/C++代码

页面模板属性: 如果提供了此属性,则它必须与覆盖规则的值匹配。在这种情况下,它将是一个工具。把它忽略似乎效果不错。我怀疑如果两个规则的名称相同,但模板不同,则可能需要这样做。你可以用这个来说明你想扩展哪一个

描述属性: 可选择的我甚至不知道它在VS GUI中的何处出现。也许只是为了记录xml文件

覆盖模式属性: 这是一个重要的问题!可以将其设置为“扩展”或“替换”。在我的例子中,我选择了Extend

xmlns属性: 必修的。如果不存在,则无法正常工作

如链接所示,然后可以提供属性、数据源和类别。请记住,类别通常按照它们在xml文件中的显示顺序显示。由于我扩展了一个现有的规则,我的自定义类别将全部显示在标准C/C++类别之后。鉴于我的工具是用于预处理文件的,我更希望在属性表的顶部有我的自定义选项。但我找不到解决这个问题的办法

请注意,您不需要ItemType/FileExtension或ContentType属性,这些属性通常用于自定义规则

因此,一旦我输入了所有这些内容,我的定制预处理选项就会显示在属性表上的标准C/C++属性旁边。请注意,所有这些新属性将与所有其他C/C++属性一起附加到ClCompile项列表

下一步是更新.props文件。我不打算讨论它,因为在创建这些自定义构建规则时,它几乎是标准的。只需知道您需要使用ClCompile项设置它们,如上所述

最后一步是让.targets文件执行我想要的操作

第一部分是通过典型条目导入自定义规则,而不是真正的导入条目:

<ItemGroup>
    <PropertyPageSchema Include="$(MSBuildThisFileDirectory)MyCustomBuild.xml" />
</ItemGroup>
然后我需要预处理每个源文件。理想情况下,最好先预处理一个文件,然后编译它——一次编译一个文件。我可以通过在自己的.targets文件中覆盖ClCompile目标来实现这一点。此目标在C:\Program Files x86下的Microsoft.cppcomon.targets文件位置下定义,具体取决于VS版本。我基本上可以将整个目标剪切粘贴到我的文件中,然后添加预处理 CL任务之前的ng任务代码。我还需要通过向ClCompile目标添加Outputs=%ClCompile.Identity属性,将目标转换为目标批处理。如果没有这一点,我的预处理任务将在转到CL任务之前在所有文件上运行,使我回到原点。最后,我需要处理预编译的头文件,因为它们需要先编译

所有这些都太痛苦了。因此,我选择了一个更简单的选项,定义一个如下所示的目标:

<Target Name="MyPreProcessingTarget"
    Condition="'@(ClCompile)' != ''"
    Outputs ="%(ClCompile.Identity)"
    DependsOnTargets="_SelectedFiles"
    BeforeTargets="ClCompile">
定义了许多属性,但最重要的是BeforeTargets=ClCompile属性。这就是在编译cpp文件之前强制执行此目标的原因

我还选择在这里进行目标批处理[Outputs=%ClCompile.Identity],因为如果我假设在我的目标中一次处理一个文件,那么做我想做的事情就更容易了

属性DependsOnTargets=\u SelectedFiles用于了解GUI用户在VS解决方案资源管理器中是否有某些选定的文件。如果是,文件将存储在由_SelectedFiles目标生成的@SelectedFiles项目列表中。通常,在解决方案资源管理器中选择特定文件并选择编译它们时,VS将强制编译它们,即使它们是最新的。我想为自动生成的预处理包含文件保留该功能,并为这些选定文件强制重新生成它们。所以我添加了这个块:

<ItemGroup Condition="'@(SelectedFiles)' != ''">
  <IncFilesToDelete Include="%(ClCompile.Filename)_pp.h"/>
</ItemGroup>
<Delete 
  Condition="'@(IncFilesToDelete)' != ''"
  Files="%(IncFilesToDelete.FullPath)" />
请注意,自动生成的include文件名为SourceFileName_pp.h。通过删除这些文件,我的预处理任务将强制重新生成它们

接下来,我从ClCompile项目列表构建一个新的项目列表,但是使用文件的_pp.h版本。我使用以下代码执行此操作:

<ItemGroup>
  <PPIncFiles
    Condition="'@(ClCompile)' != '' and '%(ClCompile.ExcludedFromBuild)' != 'true'"
    Include="%(ClCompile.Filename)_pp.h" />
</ItemGroup>
最后一部分有点难看

为了运行预处理exe,我使用标准Exec任务。但我显然只想在源文件比生成的文件更新时运行它。为此,我将源文件的众所周知的元数据ModifiedTime和生成的文件存储到两个动态属性中。但是我不能直接使用ModifiedTime元数据,因为它不是一个可比较的值。因此,我使用了以下代码,我在StackOverflow上找到了这些代码:

注意,我可以将时间戳存储在属性中,因为目标批处理的缘故,每个目标过程中项目列表只包含一个项目

最后,我可以使用Exec任务调用我的预处理器,如下所示:

<Exec 
  Condition="'@(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"  
  Command="pptool.exe [options] %(ClCompile.Identity)" />
提供选择是另一个令人头痛的问题

通常,使用[OptionName]将xml文件下定义的开关传递给.props文件下的CommandLineTemplate元数据。这将传递在xml文件下定义的属性的Switch属性。但这意味着在.targets文件下定义自己的TaskName项,该项由TaskFactory生成。但在我的例子中,我只是使用现有的Exec任务,它对我的自定义属性一无所知。在本例中,我不知道如何检索Switch属性,似乎可用的只是Name属性包含的任何内容。幸运的是,属性同时具有名称和显示名称。DisplayName是GUI用户看到的内容。因此,在xml文件下定义属性时,我只是将开关值复制到名称值中。然后,我可以使用以下方法将选项传递给Exec任务:

<Exec 
  Condition="'@(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"      
  Command="pptool.exe %(ClCompile.Option1) %(ClCompile.Option2)... %(ClCompile.Identity)" />
其中,我将所有属性定义为EnumProperty,其中EnumValue的Name=表示禁用的选项,其他EnumValue的Name=表示其他选项。不是很优雅,但我不知道怎么解决这个问题

最后,建议在自动生成文件时,.targets文件还应包括在用户清理项目时清理文件的方法。这是相当标准的,但为了方便起见,我会把它包括在这里

<PropertyGroup>
    <CleanDependsOn>$(CleanDependsOn);PPIncCleanTarget</CleanDependsOn>
</PropertyGroup>

<Target Name="PPIncCleanTarget"  Condition ="'@(ClCompile)' != ''">
  <ItemGroup>
     <PPIncFilesToDelete Include="%(ClCompile.Filename)_pp.h" />
  </ItemGroup>
  <Delete Files="%(PPIncFilesToDelete.FullPath)" Condition="'@(PPIncFilesToDelete)' != ''"/>
</Target>

对MSBuild没有太多的爱,我想

无论如何,经过多年的反复思考,我终于找到了一些东西,就在我发布我的问题后不久。关键是寻找扩展现有规则的方法,显然,我以前从未尝试过

通常,在VS中创建生成自定义项时,最终会生成3个文件:

MyCustomBuild.xml: 包含属性和开关,如VS的属性表所示

MyCustomBuild.props: 包含这些属性的默认值。可以通过使用Condition属性使它们成为有条件的

MyCustomBuild.targeters: 包含用于加载xml和目标/任务项的行

因此,第一部分是扩展现有的C/C++属性,如VisualStudio中所示。我找到了这个链接,它最终给了我一些可以使用的东西:

这里是xml位

<Rule
  Name="RuleToExend"
  DisplayName="File Properties"
  PageTemplate="generic"
  Description="File Properties"
  OverrideMode="Extend"
  xmlns="http://schemas.microsoft.com/build/2009/properties">
  <!-- Add new properties, data source, categories, etc -->
</Rule>
名称属性: Name属性必须与正在扩展的规则匹配。在本例中,我想扩展CL规则,所以我设置了该属性= CL

DisplayName属性: 这是可选的。提供时,它将覆盖属性页上显示的工具名称。在本例中,显示的工具名称为C/C++。通过设置此属性,我可以将其更改为显示我的C/C++代码

页面模板属性: 如果提供了此属性,则它必须与覆盖规则的值匹配。在这种情况下,它将是一个工具。把它忽略似乎效果不错。我怀疑如果两个规则的名称相同,但模板不同,则可能需要这样做。你可以用这个来说明你想扩展哪一个

描述属性: 可选择的我甚至不知道它在VS GUI中的何处出现。也许只是为了记录xml文件

覆盖模式属性: 这是一个重要的问题!可以将其设置为“扩展”或“替换”。在我的例子中,我选择了Extend

xmlns属性: 必修的。如果不存在,则无法正常工作

如链接所示,然后可以提供属性、数据源和类别。请记住,类别通常按照它们在xml文件中的显示顺序显示。由于我扩展了一个现有的规则,我的自定义类别将全部显示在标准C/C++类别之后。鉴于我的工具是用于预处理文件的,我更希望在属性表的顶部有我的自定义选项。但我找不到解决这个问题的办法

请注意,您不需要ItemType/FileExtension或ContentType属性,这些属性通常用于自定义规则

因此,一旦我输入了所有这些内容,我的定制预处理选项就会显示在属性表上的标准C/C++属性旁边。请注意,所有这些新属性将与所有其他C/C++属性一起附加到ClCompile项列表

下一步是更新.props文件。我不打算讨论它,因为在创建这些自定义构建规则时,它几乎是标准的。只需知道您需要使用ClCompile项设置它们,如上所述

最后一步是让.targets文件执行我想要的操作

第一部分是通过典型条目导入自定义规则,而不是真正的导入条目:

<ItemGroup>
    <PropertyPageSchema Include="$(MSBuildThisFileDirectory)MyCustomBuild.xml" />
</ItemGroup>
然后我需要预处理每个源文件。理想情况下,最好先预处理一个文件,然后编译它——一次编译一个文件。我可以通过在自己的.targets文件中覆盖ClCompile目标来实现这一点。此目标在C:\Program Files x86下的Microsoft.cppcomon.targets文件位置下定义,具体取决于VS版本。我基本上可以将整个目标剪切粘贴到我的文件中,然后在CL任务之前添加预处理任务代码。我还需要通过向ClCompile目标添加Outputs=%ClCompile.Identity属性,将目标转换为目标批处理。如果没有这一点,我的预处理任务将在转到CL任务之前在所有文件上运行,使我回到原点。最后,我需要处理预编译的头文件,因为它们需要先编译

所有这些都太痛苦了。因此,我选择了一个更简单的选项,定义一个如下所示的目标:

<Target Name="MyPreProcessingTarget"
    Condition="'@(ClCompile)' != ''"
    Outputs ="%(ClCompile.Identity)"
    DependsOnTargets="_SelectedFiles"
    BeforeTargets="ClCompile">
定义了许多属性,但最重要的是BeforeTargets=ClCompile属性。这就是在编译cpp文件之前强制执行此目标的原因

我还选择在这里进行目标批处理[Outputs=%ClCompile.Identity],因为如果我假设在我的目标中一次处理一个文件,那么做我想做的事情就更容易了

属性DependsOnTargets=\u SelectedFiles用于了解GUI用户在VS解决方案资源管理器中是否有某些选定的文件。如果是,文件将存储在由_SelectedFiles目标生成的@SelectedFiles项目列表中。通常,在解决方案资源管理器中选择特定文件并选择编译它们时,VS将强制编译它们,即使它们是最新的。我想为自动生成的预处理包含文件保留该功能,并为这些选定文件强制重新生成它们。所以我添加了这个块:

<ItemGroup Condition="'@(SelectedFiles)' != ''">
  <IncFilesToDelete Include="%(ClCompile.Filename)_pp.h"/>
</ItemGroup>
<Delete 
  Condition="'@(IncFilesToDelete)' != ''"
  Files="%(IncFilesToDelete.FullPath)" />
请注意,自动生成的include文件名为SourceFileName_pp.h。通过删除这些文件,我的预处理任务将强制重新生成它们

接下来,我从ClCompile项目列表构建一个新的项目列表,但是使用文件的_pp.h版本。我使用以下代码执行此操作:

<ItemGroup>
  <PPIncFiles
    Condition="'@(ClCompile)' != '' and '%(ClCompile.ExcludedFromBuild)' != 'true'"
    Include="%(ClCompile.Filename)_pp.h" />
</ItemGroup>
最后一部分有点难看

为了运行预处理exe,我使用标准Exec任务。但我显然只想在源文件比生成的文件更新时运行它。为此,我将源文件的众所周知的元数据ModifiedTime和生成的文件存储到两个动态属性中。但是我不能直接使用ModifiedTime元数据,因为它不是一个可比较的值。因此,我使用了以下代码,我在StackOverflow上找到了这些代码:

注意,我可以将时间戳存储在属性中,因为目标批处理的缘故,每个目标过程中项目列表只包含一个项目

最后,我可以使用Exec任务调用我的预处理器,如下所示:

<Exec 
  Condition="'@(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"  
  Command="pptool.exe [options] %(ClCompile.Identity)" />
提供选择是另一个令人头痛的问题

通常,使用[OptionName]将xml文件下定义的开关传递给.props文件下的CommandLineTemplate元数据。这将传递在xml文件下定义的属性的Switch属性。但这意味着在.targets文件下定义自己的TaskName项,该项由TaskFactory生成。但在我的例子中,我只是使用现有的Exec任务,它对我的自定义属性一无所知。在本例中,我不知道如何检索Switch属性,似乎可用的只是Name属性包含的任何内容。幸运的是,属性同时具有名称和显示名称。DisplayName是GUI用户看到的内容。因此,在xml文件下定义属性时,我只是将开关值复制到名称值中。然后,我可以使用以下方法将选项传递给Exec任务:

<Exec 
  Condition="'@(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"      
  Command="pptool.exe %(ClCompile.Option1) %(ClCompile.Option2)... %(ClCompile.Identity)" />
其中,我将所有属性定义为EnumProperty,其中EnumValue的Name=表示禁用的选项,其他EnumValue的Name=表示其他选项。不是很优雅,但我不知道怎么解决这个问题

最后,建议在自动生成文件时,.targets文件还应包括在用户清理项目时清理文件的方法。这是相当标准的,但为了方便起见,我会把它包括在这里

<PropertyGroup>
    <CleanDependsOn>$(CleanDependsOn);PPIncCleanTarget</CleanDependsOn>
</PropertyGroup>

<Target Name="PPIncCleanTarget"  Condition ="'@(ClCompile)' != ''">
  <ItemGroup>
     <PPIncFilesToDelete Include="%(ClCompile.Filename)_pp.h" />
  </ItemGroup>
  <Delete Files="%(PPIncFilesToDelete.FullPath)" Condition="'@(PPIncFilesToDelete)' != ''"/>
</Target>

为什么不直接使用cmake或scons?原始msbuild令人痛苦。msbuild是我玩过的最令人沮丧的东西之一。但是,当它开始工作时,它确实提供了一个很好的集成环境。我们只在Windows上使用Visual Studio,我就在这里。您好,我的生成工具继承标准CL MSBuild规则的所有内容是什么意思,您所指的msbuild规则是指.xxproj或xx.targets(如microsoft.cpp.targets)中的内容吗?@LanceLi MSFT我的意思是cl.xml文件位置下定义的规则各不相同。为什么不只使用cmake或scons?原始msbuild令人痛苦。msbuild是我玩过的最令人沮丧的东西之一。但是,当它开始工作时,它确实提供了一个很好的集成环境。我们只在Windows上使用Visual Studio,我现在在Windows上。您好,我的生成工具继承标准CL MSBuild规则的所有内容是什么意思,您所指的MSBuild规则是.xxproj中的内容还是.xx.targets中的内容,如microsoft.cpp.targets?@LanceLi MSFT我是说CL.xml文件位置下定义的规则不同。您好,我们很高兴听到您的问题得到解决,感谢您的分享。请将其标记为答案,这将帮助遇到相同问题的其他社区成员更轻松地搜索此有用信息,提前感谢。您好,我们很高兴听到您的问题得到解决,并感谢您的分享。请将其标记为答案,这将帮助遇到相同问题的其他社区成员更轻松地搜索此有用信息,提前感谢。