一、NuGet介绍
使用C++进行开发的同学,一定很羡慕Python的pip、Java的Maven等包管理器,只需要一个命令就可以将工程需要的依赖库安装到位。由于C++的大多数库都是系统、编译环境强相关联的,所以在开发中我们会针对不同的操作系统(Win32、Win64等)、编译器(MSVC140、MSVC120等)编译出对应的静态库或者动态库,
然后配置头文件包含目录、库引用目录、预编译宏等等。
如果一个工程引用了10个第三方库,我们需要配置这10个库的头文件包含目录、库引用目录,这无疑是很繁琐的,而且容易遗漏、出错。
今天我们介绍如何使用微软提供的NuGet工具来作为C++的库管理工具。NuGet的官网地址为:https://www.nuget.org/,任何人都可以免费注册,并上传自己制作的包供他人使用。
NuGet主要是针对.Net
平台而设计,但其也可以用来管理C++ Native的包。
NuGet管理native包的原理主要是通过导入包中的targets
文件来将预定义的配置添加到工程之中,从而简化工程配置的过程。下面是摘取curl-7.63.0-jefferyjiang包的targets文件,从这个文件中我们可以看到熟悉的Visual Studio预定义的环境变量以及配置。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- user interface -->
<ItemGroup>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)default-propertiesui.xml" />
</ItemGroup>
<!-- general -->
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)lib\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Wldap32.lib;Crypt32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<!-- v140 Win32 StaticLibrary MT Debug -->
<ItemDefinitionGroup Condition="'$(PlatformToolset)' == 'v140' And '$(Platform)' == 'Win32' And '$(Linkage)' == 'StaticLibrary_MT' And $(Configuration.IndexOf('Debug')) != -1 ">
<ClCompile>
<PreprocessorDefinitions>CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>vc140_x86_static_mt_debug\libcurld.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<!-- v140 Win32 StaticLibrary MT Release -->
<ItemDefinitionGroup Condition="'$(PlatformToolset)' == 'v140' And '$(Platform)' == 'Win32' And '$(Linkage)' == 'StaticLibrary_MT' And $(Configuration.IndexOf('Release')) != -1 ">
<ClCompile>
<PreprocessorDefinitions>CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>vc140_x86_static_mt_release\libcurl.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<!-- v140 Win32 DynamicLibrary MD Debug -->
<ItemDefinitionGroup Condition="'$(PlatformToolset)' == 'v140' And '$(Platform)' == 'Win32' And '$(Linkage)' == 'DynamicLibrary_MD' And $(Configuration.IndexOf('Debug')) != -1 ">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>vc140_x86_dynamic_md_debug\libcurld.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<!-- v140 Win32 DynamicLibrary MD Release -->
<ItemDefinitionGroup Condition="'$(PlatformToolset)' == 'v140' And '$(Platform)' == 'Win32' And '$(Linkage)' == 'DynamicLibrary_MD' And $(Configuration.IndexOf('Release')) != -1 ">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>vc140_x86_dynamic_md_release\libcurl.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<Target Name="curl_7_63_0_AfterBuild" AfterTargets="AfterBuild" />
<Target Name="curl_7_63_0_afterbuild_cmd1"
Condition="'$(PlatformToolset)' == 'v140' And '$(Platform)' == 'Win32' And '$(Linkage)' == 'DynamicLibrary_MD' And $(Configuration.IndexOf('Debug')) != -1"
AfterTargets="curl_7_63_0_AfterBuild">
<Copy SourceFiles="$(MSBuildThisFileDirectory)bin\vc140_x86_dynamic_md_debug\libcurld.dll" DestinationFiles="$(TargetDir)libcurld.dll" SkipUnchangedFiles="true" />
</Target>
<Target Name="curl_7_63_0_afterbuild_cmd2"
Condition="'$(PlatformToolset)' == 'v140' And '$(Platform)' == 'Win32' And '$(Linkage)' == 'DynamicLibrary_MD' And $(Configuration.IndexOf('Release')) != -1"
AfterTargets="curl_7_63_0_AfterBuild">
<Copy SourceFiles="$(MSBuildThisFileDirectory)bin\vc140_x86_dynamic_md_release\libcurl.dll" DestinationFiles="$(TargetDir)libcurl.dll" SkipUnchangedFiles="true" />
</Target>
</Project>
二、NuGet安装依赖包
Visual Studio中已经集成了NuGet工具,可以在菜单“工具” --> “NuGet包管理器”
中看到。经过实际使用发现,低版本的Visual Studio集成的NuGet存在部分Bug,可能会导致安装包时报错,建议使用Visual Studio 2017
(当然,“平台工具集”仍然可以选择其他的版本,如Visual Studio 2015 (v140)
等)。
具体使用方法如图,输入包名查找 --> 选中项目 -> 点击“安装”。如果包依赖其他包,NuGet会将依赖包一并安装。
三、制作NuGet包
可以先阅读官方文档:https://docs.microsoft.com/en-us/nuget/create-packages/native-packages来了解大概的结构,但由于官方文档讲解了不全面,建议下载ppx-1.0.0.1-jefferyjiang.1.0.0.5.nupkg,以其为模板来修改,将会事半功倍。
模板文件ppx_1.0.0.1
下载下来之后重命名为.zip,然后解压,(ppx_1.0.0.1表示ppx库的1.0.0.1版本,命名无要求)。
首先,修改ppx_1.0.0.1.nuspec
文件:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
<metadata>
<id>ppx-1.0.0.1-jefferyjiang</id>
<version>1.0.0.1</version>
<title>ppx-1.0.0.1</title>
<authors>jefferyjiang</authors>
<owners>jefferyjiang</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Build parameter: vc140 - Win32 - StaticLibrary|DynamicLibrary - MT|MD - Debug|Release</description>
<summary></summary>
<copyright>Copyright (c) 2017, Jeffery Jiang</copyright>
<iconUrl>https://raw.githubusercontent.com/chinajeffery/NugetLibrary/master/tomato.png</iconUrl>
<tags>native, ppx, C++</tags>
<dependencies>
<group targetFramework="native0.0">
<dependency id="curl-7.63.0-jefferyjiang" version="1.0.0.10" />
</group>
</dependencies>
</metadata>
</package>
id
为该包的唯一id,不能在NuGet仓库上传已经存在的id的包,为了避免重复,我一般会在后面加上特殊标识,类似jefferyjiang
。version
为该包的版本号,这个版本号和ppx库的版本号不同,通过包的id + version
就可以在Nuget仓库精确定位该包。
然后,将头文件添加到include目录,静态库添加到lib目录,动态库添加到bin,当然,这个目录名称和文件类型的对应关系并没有强制要求,只要ppx-1.0.0.1-jefferyjiang.targets
属性文件中的配置来正确即可。
default-propertiesui.xml
文件中定义了一些用户可以进行配置的选项,比如使用该库的静态版还是动态版,MT版还是MD版等:
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework">
<Rule Name="ProjectSettings_ppx_1_0_0_1" PageTemplate="tool" DisplayName="Nuget Library Settings" SwitchPrefix="/">
<Rule.Categories>
<Category Name="ppx_1_0_0_1_cpp" DisplayName="ppx-1.0.0.1 Settings" />
</Rule.Categories>
<Rule.DataSource>
<DataSource Persistence="ProjectFile" ItemType="" />
</Rule.DataSource>
<EnumProperty Name="Linkage" DisplayName="Linkage" Description="How to link this library, static or dynamic, mt or md." Category="ppx_1_0_0_1_cpp" >
<EnumValue Name="StaticLibrary_MT" DisplayName="Static Library With MT" Description="Static Library">
</EnumValue>
<EnumValue Name="DynamicLibrary_MD" DisplayName="Dynamic Library With MD" Description="Dynamic Library">
</EnumValue>
</EnumProperty>
</Rule>
</ProjectSchemaDefinitions>
在成功安装该包以后,可以在项目属性中看到这些配置选项:
最后就是ppx-1.0.0.1-jefferyjiang.targets
文件了,该文件名必须和包的id一样。该文件的配置语法并不难理解,唯一需要注意的是,在这个文件中可以使用default-propertiesui.xml
文件中定义的变量,如:
'$(Linkage)' == 'DynamicLibrary_MD'