使用MSBudio和VisualStudio 2012 +预处理C++文件
我有一系列标准cpp文件,每个文件都包含一个特定于文件的include语句。但是,在调用标准C++编译器之前,必须包含一个预处理工具来填充这些文件。p> 棘手的是,我希望使用MSBuild将其完全集成到VisualStudio中。因此,当我在CPP文件上打开VisualStudio的属性窗口时,我希望看到所有标准的C++编译器选项,并且理想地,一些控制预处理器工具的自定义属性。作为面向对象的类比,我希望我的构建工具继承标准CL-MSBuild规则的所有内容,并向其中添加一些自定义属性和构建步骤使用MSBudio和VisualStudio 2012 +预处理C++文件,c++,visual-studio,msbuild,C++,Visual Studio,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文件位置下定义的规则不同。您好,我们很高兴听到您的问题得到解决,感谢您的分享。请将其标记为答案,这将帮助遇到相同问题的其他社区成员更轻松地搜索此有用信息,提前感谢。您好,我们很高兴听到您的问题得到解决,并感谢您的分享。请将其标记为答案,这将帮助遇到相同问题的其他社区成员更轻松地搜索此有用信息,提前感谢。