1.前言
一直想写一篇关于visual studio项目文件结构格式和props文件用法的文章,那么开始吧。
- C++项目文件后缀为vcxproj,C#项目文件后缀为csproj,两者大同小异,都为xml格式;
- 项目配置文件中包含对应项目依赖的编译器版本,项目环境配置,编译配置、包含文件等内容;
- 官方不提倡直接修改项目文件内容,因为格式结构有很多规则,如果不了解格式结构和规则的话,很容易改出问题;
- 当然一些场景下,直接编辑项目文件会很便利;
- 官方提倡用户配置自己的.props文件,并在项目文件中import进去即可,这样相当于给了一个“官方的”编辑入口,当然.props文件也是可以复用的,将通用的配置写入.props文件中供多个项目使用,是不是很便利?好了,有种抽象封装的味道了;
2.项目结构
首先,看一下官方对于.vcxproj和.props结构的描述,
.vcxproj and .props file structure
下面简单总结下里面的内容,(本来想的简单总结,但是总结的过程中发现虽然有些条目官方描述讲解的比较啰嗦,但是其目的是为了让读者更好的理解,所谓苦口婆心也,写着写着就变成了半总结半自主翻译的风格了=-=)
- 当用visual c++创建新项目时,会创建.vcxproj文件,这是xml格式的;在.vcxproj中可以import进来.props和.targets文件;
- 官方建议只用IDE来创建和修改.vcxproj文件,尽可能用visual studio的属性配置页来更改.vcxproj文件;
- 基于MSBuild的项目都支持包含.props和.targets文件,如果你一定想自己编辑项目文件,那么编辑这个好了;
- 项目文件内容和结构上有很多限制,如果一定要编辑,要充分熟悉MSBuild并且遵循本文(即上面官方链接中的文章,下同)的指导,否则很容易出错;
你懂得,话都说到了这个地步,你不要改啊,很容易出错的,这种格式不是为了手动编辑而设计的,真的...如果一定要改,那么遵循本文的指导吧,接下来就是关于项目文件和相关文件结构和基础内容的说明;
重要!即将开始!
- 项目文件的结构必须符合规定的格式,如本文所述;
- Visual Studio C++项目最近不支持通配符和直接罗列条目项了,比如这种不被支持了,
<ItemGroup>
<None Include="*.txt"/>
<ClCompile Include="a.cpp;b.cpp"/>
</ItemGroup>
- Visual Studio C++项目最近在项目文件路径中不支持宏了,比如这种不被支持了,值得说明的是,这里的“不支持”是不保证宏在IDE的所有操作中都能如期工作,并且在.props文件中宏还是被支持的;
<ItemGroup>
<ClCompile Include="$(IntDir)\generated.cpp"/>
</ItemGroup>
- 项目文件中针对项目配置会有多个分开的Group,每个Group的条件必须是如下的形式,
Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"
- 每条property都应该以正确的标签标注,并放在group里,像 property rule file描述的那样;
开始了!
- 首先注意项目文件中顶级元素都有特定的顺序,
- 大部分的property groups和item definition groups出现在import Microsoft.Cpp.Default.props之后(原因如下文);
- 所有的targets在项目文件的最后import进去;
- 这里有多种property groups,其有着特定的顺序和唯一的标签;
- 项目文件中元素的顺序是极其重要的,因为MSBuild是基于顺序评估模式的,如果你的项目文件(包含import进来.props和.targets文件)中对于某条property有多处定义,那么最后一条定义将重写之前的定义,即生效的是最后一条定义,说道这里我之前犯过错误,自己再.props文件中定义的一个变量值是正确的,但是VS却识别成了其他的值,最后发现是其他的.props文件中也有该条变量的定义,也就是“正确的值”被“错误的值”覆盖了;
- 这是一份最小的.vcxproj文件,其顶级元素也按照规定的顺序排列,
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
<ItemGroup Label="ProjectConfigurations" />
<PropertyGroup Label="Globals" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.default.props" />
<PropertyGroup Label="Configuration" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>
接下来的章节将会介绍这些元素的作用,也会解释为什么它们要以这样的顺序排列!
-
ProjectConfigurations ItemGroup element
<ItemGroup Label="ProjectConfigurations" />
项目配置group包含了项目配置相关的描述,比如Debug|Win32, Release|Win32, Debug|ARM等,一些项目的设置仅针对特定的项目配置,比如你将会正确的想设置release环境的编译优化项,而此不会针对debug环境;
ProjectConfigurations在编译阶段就不会起作用了,也可以将这个元素放到.props文件中,这样如果想修改该项的话就不能在编译器中修改了,而需要手动修改;
-
ProjectConfiguration elements
项目配置包含配置和平台,比如release和x64,如下,
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
你可以在Solution Configuration Manager.中管理、编辑项目配置;
-
Globals PropertyGroup element
<PropertyGroup Label="Globals" />
Globals包含项目级的设置,比如ProjectGuid
, RootNamespace
, and ApplicationType
or ApplicationTypeRevision,
后两个经常指的是操作系统,最近一个项目只能指定一种操作系统;
-
Microsoft.Cpp.default.props Import element
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.default.props" />
Microsoft.Cpp.default.props是不能修改的!它包含了项目默认的设置,而这些默认设置可能依赖于ApplicationType的不同而有不同值;
-
Configuration PropertyGroup elements
<PropertyGroup Label="Configuration" />
Configuration
property group附加有条件(比如 Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"
),并且每一个Configuration
中会有多分,这种property group针对特定的Configuration
表示了不同的设置. Configuration properties包含了PlatformToolset、系统 property sheets(在Microsoft.Cpp.props中)的包含情况,比如, 如果你定义了 这种设置<CharacterSet>Unicode</CharacterSet>
,那么系统 property sheet microsoft.Cpp.unicodesupport.props 将会被包含进来. 如果你查看了Microsoft.Cpp.props的内容,你将会看到:<Import Condition="'$(CharacterSet)' == 'Unicode'" Project="$(VCTargetsPath)\microsoft.Cpp.unicodesupport.props" />
.,这就是上述情况产生的原因!
-
Microsoft.Cpp.props Import element
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
Microsoft.Cpp.props (直接或通过imports)定义了很多tool-specific properties的默认值,比如编译的优化选项、编译的警告等级、MIDL tool's TypeLibraryName property等,并且根据与它紧邻的之前的configuration properties而imports多个系统属性表;比如下面的,其实Microsoft.Cpp.props Import element的该部分功能和Microsoft.Cpp.default.props Import element有相似之处;
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PlatformToolset>v110</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
-
ExtensionSettings ImportGroup element
<ImportGroup Label="ExtensionSettings" />
ExtensionSettings组包含作为【自定义编译生成配置的一部分】的属性表的导入。自定义编译生成配置最多由三个文件定义:.targets文件、.props文件和.xml文件;这个导入组包含.props文件的导入;
-
PropertySheets ImportGroup elements
<ImportGroup Label="PropertySheets" />
这个和用户通过VS的Property Manager 添加的属性表有关系;
-
UserMacros PropertyGroup element
<PropertyGroup Label="UserMacros" />
UserMacros包含了你为自定义编译过程而定义的一些变量,比如你可以定义变量$(CustomOutputPath)用作输出目录或者用作定义其他变量;这个属性组包含了若干条属性;在VS的项目文件中这个组是没有内容的,这是因为visual c++不在支持宏用作配置,但是在属性表(.props)中宏是被支持的;想一下,如果你不了解此规则的情况下,在项目文件的UserMacros PropertyGroup中定义了宏,那么会有什么样的行为?!
-
Per-configuration PropertyGroup elements
<PropertyGroup />
这里信息量有点大...
这里有这种property group的多个实例, 对于所有的project configurations,有一个configuration;每一个property group必须附加有条件;If any configurations are missing, the Project Properties dialog won't work correctly. Unlike the property groups listed before, this one doesn't have a label. 该组包含了项目配置级别的设置. These settings apply to all files that are part of the specified item group. Build customization item definition metadata is initialized here.
This PropertyGroup must come after <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
and there must be no other PropertyGroup without a Label before it (otherwise Project Properties editing won't work correctly).由于.props文件中经常会定义该组,这也就是为什么一般.props文件都在<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
之后import的缘由了;
-
Per-configuration ItemDefinitionGroup elements
<ItemDefinitionGroup />
包含条目定义。这些定义必须符合和没有标签的单个配置元素-PropertyGroup
相同的条件规则。
-
ItemGroup elements
<ItemGroup />
ItemGroup
包含项目中的项(比如源文件和其他);项目中的项不支持条件设置(这是因为规则定义将这些项类型处理为了项目项);
每一个配置的元数据应该包含配置条件,即使它们是一样的,比如,
<ItemGroup>
<ClCompile Include="stdafx.cpp">
<TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</TreatWarningAsError>
<TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</TreatWarningAsError>
</ClCompile>
</ItemGroup>
Visual Studio C++最近在项目项中不再支持通配符,
<ItemGroup>
<ClCompile Include="*.cpp"> <!--Error-->
</ItemGroup>
Visual Studio C++最近在项目项中也不再支持宏,
<ItemGroup>
<ClCompile Include="$(IntDir)\generated.cpp"> <!--not guaranteed to work in all scenarios-->
</ItemGroup>
References被指定在ItemGroup里,并且有以下限制,
- References不支持条件配置;
- References元数据不支持条件配置;
-
Microsoft.Cpp.targets Import element
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
定义(直接或通过imports)C++ targets,像生成、清理等。
-
ExtensionTargets ImportGroup element
<ImportGroup Label="ExtensionTargets" />
这个组包含自定义生成目标文件的导入。
-
Impact of incorrect ordering
Visual Studio IDE依赖于项目文件有事先描述好的顺序,比如,当你通过IDE的属性页定义一条属性值得时候,IDE通常会把这条属性定义放在没有标签的属性组里面。这种顺序保证了用户定义的属性值能够重写被系统属性表设置的相应的默认属性值。类似的,目标文件在项目文件的结尾处被import进来,这是因为他们一来于之前定义的属性,并且通常它们自己不会定义属性。同样的,用户的.props文件总是在系统属性表(被Microsoft.Cpp.props
包含进来的)之后被import进来,这为了确保用户的能够重写被系统属性表设置的默认值。
如果.vcxproj文件不遵循这种布局,编译结果可能不会是你所期望的。比如,如果你错误的在系统属性表之后import了用户定义的属性表,那么用户设置的属性值将会被相应的系统属性表中定义的属性值所覆盖。
即使是IDE设计时的经验也在一定程度上依赖于元素的正确顺序。比如,如果你的.vcxproj文件没有包含import属性表的组,IDE无法确定要把用户通过属性管理器创建的属性表放在哪里。这会导致用户的属性表被系统的属性表所覆盖。尽管IDE使用的启发式方法可以容忍.vcxproj文件布局中的轻微不一致,我们仍强烈的建议你不要偏离上问中描述的结构。
-
How the IDE uses element labels
在IDE中,当你在通用属性页中设置UseOfAtl属性时,它会被写入项目文件中的Configuration属性组。同一属性页中的目标名属性被写入每个配置的无标签属性组中。Visual Studio会查看属性页的xml文件,以获得在何处写入每个属性的信息。
-
Property Sheet layout
下面的xml片段是一个最小的.props文件布局,它和.vcxproj文件很接近,并且.props中的元素的功能可以继承上文中描述的那样。
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup />
</Project>
为了拥有你自己的.props文件,在vcTargets文件夹下的.props文件中复制一个,然后修改为你所需的。对于Visual Studio 2019 企业版默认的vcTargets路径是%ProgramFiles%\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\VC\VCTargets
。
3.总结
终于半总结半翻译的完成了这篇文章,huooooo!。
官网对.vcxproj和.props文件的结构和里面包含的元素的功能作用已经有一个比较清晰的说明了,当然如果有一定的经验的情况下,再看这篇文章会有更深入的理解。
下一篇文章将会讲述在实际项目中该怎么配置自己的.props文件,什么情况下编辑.vcxproj文件可以有便利性和可靠性的最大效益。
欢迎继续关注。