MSBuild 与遗留工具链折腾笔记
MSBuild 与遗留工具链折腾笔记
MSBuild(Microsoft Build Engine)是 Visual Studio 背后的构建系统。做老引擎移植、跨平台工程时经常要直接和 .vcxproj、工具集打交道,下面是几条实用经验。
.vcxproj 文件结构速览
工程文件本质是个 XML,所有工程信息都用 XML 元素描述,大致分三类:
- Item——所有参与构建的文件放在
ItemGroup里(杂项文件也可以放进 item)。- Item Metadata——item 的元数据放在
ItemDefinitionGroup。比如对ClCompileitem,可以指定WarningLevel、编译选项等元数据,告诉编译器怎么编它们。
- Item Metadata——item 的元数据放在
- Property——构建配置(如编译器版本)放在
PropertyGroup。可以自定义任意属性,用$(prop_name)或%(prop_name)引用。 - Import——类似 C++ 的
#include,把任意合法元素引进当前文件。
上面这些元素都能带 Condition 属性来限定生效范围,例如:
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='D3D11Debug|Durango'">
...
</ItemDefinitionGroup>
用 .props 组织多平台配置
多平台一上来配置就爆炸:3 个平台 × 5 个配置 = 15 套配置,外加各自的 item definition,重复一大堆。把公共属性抽到 .props 文件里、再 Import 进来,能显著减少重复,改一处生效多处。
用代码批量管理工程文件
批量升级 / 改一堆 .vcxproj 是重复又易错的活,可以用 MSBuild API 自动化。重点是 Microsoft.Build.Construction 提供的 Project Object Model(POM),能很方便地读写工程属性。
写自动化脚本时引用 Microsoft.Build.dll(在 VS 的 MSBuild bin 目录下,老版本如 C:\Program Files (x86)\MSBuild\14.0\Bin\Microsoft.Build.dll),用 C# 配合 LINQ 处理工程集合就很顺。具体 API 按需查文档即可。
在新版 VS 里用老编译器工具集
有时受遗留编译器设置、库依赖限制,没法直接上最新版 VS。但老版本 VS(VS2005/2008)又难用、不稳、工程依赖一团乱。好在新版 VS 允许扩展编译器工具集——可以在新 IDE 里挂任意版本的编译器来构建。
官方 release 直接支持的最老工具集是 VS2008。我之前遇到过一个用 VS2005 写的遗留工具项目,就自己做了个扩展把 VS2005(v80)工具集加进来。步骤:
- 装 VS2005,要用到它的头文件、库、编译器。
- 装 VS2010——只有 VS2010 自带从 VCBuild 迁移到 MSBuild 的脚本。
- 装你真正想用的新版 VS。
- 把工具集文件放到:
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Platforms\Win32\PlatformToolsets\v80\ - x64 的放到:
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Platforms\x64\PlatformToolsets\v80\ - 用 VS2010 迁移老工程——这一步必须用 VS2010,它对老工程的兼容性最好。
之后就能在工程配置里看到并选用 v80(VS2005)工具集了。
坑:VS2005/2008 工具集只支持 .NET Framework 2.0 / 3.0 / 3.5。迁移完工程后必须显式指定 .NET 版本,否则会默认用 4.0。更别扭的是,这个 .NET 版本只能在工具集切到 VS2017 时才能改——改完再切回 v80。
参考:
- Stuck on an older toolset version? Move to VS2015 without upgrading your toolset
- C++ Native Multi-targeting
用 init_seg 控制全局变量初始化顺序
代码里有时需要初始化全局静态对象,而对象之间又有初始化先后依赖。C++ 标准不保证跨翻译单元的全局初始化顺序,这时可以用 #pragma init_seg 指定构造时机——在 DLL 或需要初始化的库里尤其重要。
具体语义和 compiler/lib/user 三档的区别见 官方文档。