使用 MSBuild 和 Visual Studio 2012+ 预处理 C++ 文件 [英] Pre-Processing C++ file using MSBuild and Visual Studio 2012+

查看:53
本文介绍了使用 MSBuild 和 Visual Studio 2012+ 预处理 C++ 文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一系列标准的 cpp 文件,每个文件都包含一个特定于文件的 #include 语句.但是,在调用标准 C++ 编译器之前,这些包含文件的内容必须由预处理工具填充.

I have a series of standard cpp files and each of these files contain a file-specific #include statement. However, the content of those included files must be populated by a pre-processing tool prior to invoking the standard C++ compiler.

棘手的部分是我希望使用 MSBuild 将其完全集成到 Visual Studio 中.因此,当我在 cpp 文件上打开 Visual Studio 的属性窗口时,我想查看所有标准的 C++ 编译器选项,理想情况下还有一些控制预处理器工具的自定义属性.作为 OOP 的类比,我希望我的构建工具继承标准 CL MSBuild 规则的所有内容,并添加一些自定义属性 &构建步骤.

The tricky part is that I want this to be fully integrated into Visual Studio using MSBuild. Therefore, when I bring up Visual Studio's Property Window on a cpp file, I want to see all the standard C++ compiler options and, ideally, some Custom Properties controlling the pre-processor tool. As a OOP analogy, I want my build tool to inherit everything from the standard CL MSBuild Rule, and add some Custom Properties & Build Steps to it.

我通过一个极其费力的过程成功地做到了这一点,即基本上创建自定义 MSBuild 规则并将大部分 C++ 选项复制/粘贴到我的自定义规则中.最后,我通过 MSBuild .props 文件中的 CommandLineTemplate 条目将数百万个 C++ 选项传递给标准 C++ 编译器.它非常复杂,而且 C++ 选项不会在我更新 Visual Studio 时自动更新.

I have successfully done this through an extremely laborious process of basically creating a Custom MSBuild Rule and Copy/Paste most of the C++ Options into my Custom Rule. Finally, I then pass along the million C++ Options to the standard C++ compiler through the CommandLineTemplate entry in my MSBuild .props file. It's ridiculously complicated and the C++ options don't automatically get updated as I update Visual Studio.

我可以找到大量自定义 MSBuild 规则的示例,但我无法找到一个可以搭载到现有规则的示例.

I can find plenty of examples of Custom MSBuild Rules, but I haven't been able to find one where it piggybacks onto an existing one.

推荐答案

不太喜欢 MSBuild,我接受...

Not a lot of love for MSBuild, I take it...

无论如何,经过多年的来回,我终于找到了一些东西,在我发布我的问题后不久.关键是搜索扩展"现有规则,显然,我以前没有尝试过.

Anyway, after years of going back and forth on that one, I finally found something, soon after I posted my question. The key was to search for "extending" existing Rule which, apparently, I hadn't tried before.

通常,当您在 VS 中创建 Build Customization 时,您最终会得到 3 个文件:

Usually, when you create a Build Customization in VS, you end up with 3 files:

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

MyCustomBuild.xml:
Contains the Properties & Switches, as shown on the VS's Property Sheet.

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

MyCustomBuild.props:
Contains default values for those Properties. They can be made conditional through the use of the Condition attribute.

MyCustomBuild.targers:
包含一行加载您的 xml 和目标/任务条目.

MyCustomBuild.targers:
Contains a line to load up your xml and the Target/Task entries.

所以第一部分是扩展现有的 C/C++ 属性,如 Visual Studio 中所示.我找到了这个链接,它终于给了我一些可以使用的东西:https://github.com/Microsoft/VSProjectSystem/blob/master/doc/extensibility/extending_rules.md

So the first part was to extend the existing C/C++ Properties as shown in Visual Studio. I found this link, which finally gave me something to work with: https://github.com/Microsoft/VSProjectSystem/blob/master/doc/extensibility/extending_rules.md

这是 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".

Name attribute:
The Name attribute must match the rule being extended. In this case, I wanted to extend the CL rule, so I set that attribute = "CL".

显示名称属性:
这是可选的.提供时,它将覆盖在属性表上看到的工具名称.在这种情况下,显示的工具名称是C/C++".我可以通过设置此属性将其更改为显示我的 C/C++".

DisplayName attribute:
This is optional. When provided, it will overwrite the tool's name seen on the Property Sheet. In this case, the tool name shown is "C/C++". I can change it to show "My C/C++" by setting this attribute.

PageTemplate 属性:
如果提供此属性,它必须与覆盖规则的值相匹配.在这种情况下,它将是工具".把它放在外面似乎工作正常.我怀疑如果 2 个规则名称相同但模板不同,则可能需要 .您可以使用它来阐明您想要扩展哪一个.

PageTemplate attribute:
If this attribute is provided, it must match the overwritten rule's value. In this case, it would be "tool". Just leaving it out seems to work fine. I suspect this could be needed if 2 rules had the same name, but a different template. You could use this to clarify which one you wanted to extend.

描述属性:
可选的.我不知道它甚至出现在 VS GUI 中的什么地方.也许只是为了记录 xml 文件.

Description attribute:
Optional. I don't know where that even shows up within the VS GUI. Maybe it's just for documenting the xml file.

OverrideMode 属性:
这是一个重要的!它可以设置为扩展"或替换".就我而言,我选择了扩展".

OverrideMode attribute:
This is an important one! It can be set to either "Extend" or "Replace". In my case, I chose "Extend".

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

xmlns attribute:
Required. Doesn't work properly if not present.

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

As the link suggest, you can then provide the properties, data source and categories. Keep in mind that categories are usually displayed in the order they appear in the xml file. Since I was extending an existing Rule, my custom categories would all show up after the standard C/C++ categories. Given that my tool is for pre-processing the files, I would have preferred having my custom options at the top of the Property Sheet. But I couldn't find way around that.

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

Note that you do NOT need the ItemType/FileExtension or ContenType Properties, typically found for custom Rules.

因此,一旦我输入了所有这些,我的自定义预处理选项就会出现在属性表上的标准 C/C++ 属性旁边.请注意,所有这些新属性都将与所有其他 C/C++ 属性一起附加到ClCompile"项列表.

So once I entered all of that, my custom pre-processing options showed up alongside the standard C/C++ properties on the Property Sheet. Note that all these new properties would be attached to the "ClCompile" Item list, with all the other C/C++ properties.

下一步是更新 .props 文件.我不会进入它,因为它在创建这些自定义构建规则时非常标准.只要知道您需要使用ClCompile"项来设置它们,如上所述.

The next step was to update the .props file. I'm not going to get into it since it's pretty much standard when create these custom build Rules. Just know that you need to set them using the "ClCompile" Item, as mentioned above.

最后一步是让 .targets 文件做我想做的事.

The final step was to get the .targets file to do what I wanted.

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

The first part was to "import" (not really an import entry) the custom Rule through the typical entry:

<ItemGroup>
    <PropertyPageSchema Include="$(MSBuildThisFileDirectory)MyCustomBuild.xml" />
</ItemGroup>

然后我需要预处理每个源文件.理想情况下,预处理一个文件然后编译它会更好 - 一次一个文件.我本可以通过覆盖我自己的 .targets 文件中的ClCompile"目标来做到这一点.此目标在Microsoft.CppCommon.targets"文件下定义(C:\Program Files (x86)"下的位置因VS 版本而异).我基本上可以有 Cut &将整个 Target 粘贴到我的文件中,然后在CL"任务之前添加我的预处理任务代码.我还需要通过将Outputs=%(ClCompile.Identity)"属性添加到ClCompile"目标来将目标转换为目标批次.如果没有这个,我的预处理任务将在继续执行CL"任务之前运行所有文件,让我回到第一个.最后,我需要处理预编译的头文件,因为它们需要先被编译.

Then I needed to pre-process every source file. Ideally, it would have been nicer to pre-process a file and then compile it - one file a time. I could have done this by overwriting the "ClCompile" target within my own .targets file. This Target is defined under the "Microsoft.CppCommon.targets" file (location under "C:\Program Files (x86)" varies, depending on the VS version). I basically could have Cut & Pasted the whole Target into my file, then add my pre-processing task code before the "CL" Task. I also would have needed to convert the Target into a Target Batch, by adding an "Outputs=%(ClCompile.Identity)" attribute to the "ClCompile" Target. Without this, my pre-processing task would have ran on all files before moving on to the "CL" task, bringing me back to square one. Finally, I would have needed to deal with Pre-Compiled Header files, since they need to be compiled first.

这一切都太痛苦了.所以我选择了更简单的定义 Target 选项,如下所示:

All of this was just too much of a pain. So I selected the simpler option of defining a Target which looks like this:

<Target Name="MyPreProcessingTarget"
    Condition="'@(ClCompile)' != ''"
    Outputs ="%(ClCompile.Identity)"
    DependsOnTargets="_SelectedFiles"
    BeforeTargets="ClCompile">

定义了许多属性,但最重要的是 BeforeTargets="ClCompile" 属性.这就是强制此目标在编译 cpp 文件之前执行的原因.

There are a number of attributes defined but the most important one is the BeforeTargets="ClCompile" attribute. This is what forces this target to execute before the cpp files are compiled.

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

I also chose to do a Target Batch processing here [Outputs ="%(ClCompile.Identity)"] because it was just easier to do what I wanted to do, if I assumed to have 1 file being processed at a time, in my Target.

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

The attribute DependsOnTargets="_SelectedFiles" is used to know if a GUI user has some selected file within VS Solution Explorer. If so, the files will be stored in the @(SelectedFiles) Item List (generated by the "_SelectedFiles" Target). Typically, when selecting specific files within the Solution Explorer and choosing to compile them, VS will forcefully compile them even if they are up-to-date. I wanted to preserve that functionality for the automatically-generated pre-processed include files, and forcefully regenerate them as well, for those selected files. So I added this block:

<ItemGroup Condition="'@(SelectedFiles)' != ''">
  <IncFilesToDelete Include="%(ClCompile.Filename)_pp.h"/>
</ItemGroup>
<Delete 
  Condition="'@(IncFilesToDelete)' != ''"
  Files="%(IncFilesToDelete.FullPath)" />

请注意,自动生成的包含文件名为 SourceFileName_pp.h.通过删除这些文件,我的预处理任务将强制重新生成它们.

Note that the automatically-generated include files are named SourceFileName_pp.h. By deleting those files, my pre-processing Task will forcefully re-generate them.

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

Next, I build a new Item list from the "ClCompile" Item list, but with the "_pp.h" versions of the files. I do so with the following code:

<ItemGroup>
  <PPIncFiles
    Condition="'@(ClCompile)' != '' and '%(ClCompile.ExcludedFromBuild)' != 'true'"
    Include="%(ClCompile.Filename)_pp.h" />
</ItemGroup>

最后一部分有点丑.

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

In order to run my pre-processing exe, I use the standard "Exec" Task. But I obviously only want to run it if the source file is newer than the generated file. I do so by storing the well-known metadata "ModifiedTime" of the source file, and the generated file into a couple of dynamic Properties. But I can't use the ModifiedTime metadata directly, as it's not an comparable value. So I used the following code, which I have found on StackOverflow here: Comparing DateTime stamps in Msbuild

<PropertyGroup>
  <SourceFileDate>$([System.DateTime]::Parse('%(ClCompile.ModifiedTime)').Ticks)</SourceFileDate>
  <PPIncFileDate Condition="!Exists(%(PPIncFiles.Identity))">0</PPIncFileDate>  
  <PPIncFileDate Condition="Exists(%PPIncFiles.Identity))">$([System.DateTime]::Parse('%(PPIncFiles.ModifiedTime)').Ticks)</PPIncFileDate>  
</PropertyGroup>

请注意,我可以将时间戳存储在属性中,因为由于目标批处理,项目列表每个目标传递只包含一个项目.

Note that I can store the timestamps in Properties, given that the Item Lists only contain one Item per Target Pass, because of Target Batching.

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

Finally, I can invoke my pre-processor using the "Exec" Task, as follows:

<Exec 
  Condition="'@(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"  
  Command="pptool.exe [options] %(ClCompile.Identity)" />

提供选项是另一个令人头疼的问题.

Supplying the options was yet, another headache.

通常,在 xml 文件下定义的开关只是使用 [OptionName] 传递给 .props 文件下的CommandLineTemplate"元数据.这将传递在 xml 文件下定义的 Property 的Switch"属性.但这意味着在 .targets 文件下定义您自己的 TaskName 项目,由 TaskFactory 制作.但就我而言,我只是使用现有的Exec"任务,它对我的​​自定义属性一无所知.在这种情况下,我不知道如何检索Switch"属性,而似乎可用的只是Name"属性包含的任何内容.幸运的是,属性同时具有名称和显示名称.DisplayName 是 GUI 用户看到的.所以在xml文件下定义Properties时,我只是将Switch"值复制到Name"值中.然后我可以使用以下内容将选项传递给 Exec 任务:

Typically, the switches as defined under the xml file are just passed to a "CommandLineTemplate" metadata under the .props file using [OptionName]. This will pass the "Switch" attribute of the Property defined under the xml file. But that implies defining your own TaskName item, made from a TaskFactory, under the .targets file. But in my case, I was just using the existing "Exec" Task, which doesn't know anything about my custom Properties. I didn't know how to retrieve the "Switch" attribute in this case, and what seems to be available is just whatever the "Name" attribute contains. Luckily, a Property has both a Name and a DisplayName. The DisplayName is what the GUI user sees. So I just copied the "Switch" value into the "Name" value, when defining the Properties under the xml file. I could then pass the option to the Exec Task using something like:

<Exec 
  Condition="'@(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"      
  Command="pptool.exe %(ClCompile.Option1) %(ClCompile.Option2)... %(ClCompile.Identity)" />

我将所有属性定义为EnumProperty",EnumValue"具有 Name="" 用于禁用选项,其他 EnumValue 具有 Name="switch" 用于其他.不是很优雅,但我不知道解决这个问题的方法.

Where I defined all my Properties as "EnumProperty", with an "EnumValue" having Name="" for disabled options, and other EnumValue having Name="switch" for the others. Not very elegant, but I didn't know a way around this.

最后,建议在自动生成文件的时候,.targets 文件中还应该包含用户清理项目时清理的方式.这是非常标准的,但为了方便起见,我将其包括在此处.

Finally, it is recommended that when automatically generating files, the .targets file should also include a way to clean them up when the user Cleans up the Project. That's pretty standard but I'll include it here for convenience.

<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 和 Visual Studio 2012+ 预处理 C++ 文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆