将你的C++库发布到NuGet全攻略

在 C++领域,目前最流行的库管理工具是 vcpkg。但是,Visual Studio 对 vcpkg 并不原生支持,需要进行繁琐的文件和命令行配置,不同项目甚至还依赖不同 vcpkg 版本,远不如 NuGet 简明易懂。但是,NuGet 主要是面向.NET 的库管理工具。尽管支持 C++,但提供的文档超级稀少晦涩。本文会为你提供完整新手入门攻略。实事求是地说,将 C++库发布到 NuGet 未必比 vcpkg 简便;它提供的方便主要是面向库用户而非库开发者。如果你愿意为了用户的方便宁可自己麻烦一些,那 NuGet 就是适合你的选择!

建立NuGet目录结构

发布到 NuGet 的库必须遵守特定的目录结构。此处用“”表明库的根目录。根目录下应包含以下内容:

C 库名.nuspec

nuspec 文件的内容格式 官网有详细文档 ,此处不赘述,只提供一个示例:

<?xml version="1.0" encoding="utf-8"?>

<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">

<metadata>

<id>MATLAB.MexTools</id>

<version>7.1.1</version>

<description>

使用本工具快速生成 MATLAB C++

支持的平台:x64。支持的平台工具集:v143 v145。依赖 Visual C++

</description>

<authors>埃博拉酱</authors>

<projectUrl>https://github.com/Ebola-Chan-bot/MexTools</projectUrl>

<tags>native Static v143 v145 x64</tags>

<icon>图标.png</icon>

<license type="expression">MIT</license>

<summary>使用本工具快速生成 MATLAB C++ MEX 数据 API 文件函数</summary>

<releaseNotes>

修复 WindowsErrorMessage 并不使用输入的错误码的问题 </releaseNotes>

<readme>README.md</readme>

<dependencies>

<dependency id="native.magic_enum" version="0.9.5" />

</dependencies>

</metadata>

</package>

#技术分享id 就是你的库名,应该与 nuspec 文件名匹配。对于 tags,约定俗成地,对于 C++库应该至少添加一个 native 标签,但并非强制。icon 和 readme 路径可以使用相对路径,相对于库根目录。

图标.png

这个图标文件可以有任意名称,只要在 nuspec icon 中指定即可。这个图标将在用户在 NuGet 上浏览到你的库时显示。

README.md

跟图标类似,可以有任意名称,只要在 nuspec readme 中指定即可。这个文件将在用户使用 Visual Studio 安装了你的库之后自动打开。但是,默认不会渲染 MarkDown 格式,所以也可以指定为 txt 纯文本。

uild
ative

此目录下包含你库的主体内容,无论是头文件、静态库、动态库还是其它数据。这些文件的组织没有强制要求,你可以按照你的喜好任意放置。下面的 targets 文件将指导 MSBuild 该如何将你的文件加入编译过程。

TypeScript uild
ative库名.targets

该文件将被用户的 vcxproj 项目文件引用,并在编译时指导编译器如何将你的库加入编译。示例:

<?xml version="1.0" encoding="utf-8"?>

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<ItemDefinitionGroup>

<ClCompile>

<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

<PreprocessorDefinitions>MARIADB_STATIC_LINK;%(PreprocessorDefinitions)</PreprocessorDefinitions>

</ClCompile>

<ResourceCompile>

<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

<PreprocessorDefinitions>MARIADB_STATIC_LINK;%(PreprocessorDefinitions)</PreprocessorDefinitions>

</ResourceCompile>

</ItemDefinitionGroup>

<ItemDefinitionGroup>

<Link>

<AdditionalDependencies>Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>

</Link>

</ItemDefinitionGroup>

<Target Name="设置链接输入" BeforeTargets="Link">

<PropertyGroup>

<RuntimeLibrary>%(ClCompile.RuntimeLibrary)</RuntimeLibrary>

</PropertyGroup>

<ItemGroup>

<Link Update="@(Link)">

<AdditionalDependencies>$(MSBuildThisFileDirectory)MariaDB_Connector.$(RuntimeLibrary).$(PlatformToolsetVersion).lib;%(Link.AdditionalDependencies)</AdditionalDependencies>

</Link>

</ItemGroup>

</Target>

</Project>

稍后会在讲解 MSBuild 流程时解释此文件的内容和意义。

二进制兼容性问题

如果你的库以纯源码形式提供,可以跳过本节。如果你的库是预先编译的,由于 C++没有强制规定二进制接口,不同编译器和不同版本之间可能会产生不兼容问题。对于 MSVC 来说,你需要思考如下兼容性问题:

运行库

此设置影响你的目标用户是否需要额外安装 Microsoft Visual C++ 可再发行程序包 ,以及是否能以调试模式链接你的库。在项目 配置属性C/C++代码生成运行库 中提供4种依赖方式:

  • 多线程(/MT),使用此方式意味着用户无需安装可再发行程序包,所有必要的运行库代码一并编译到文件中,这会导致生成的文件较大,但性能是最高的。此选项不支持调试。
  • 多线程调试(/MTd),类似于MT,但支持调试,但生成的代码性能较低。
  • 多线程DLL(/MD),用户必须安装可再发行程序包,由于运行库代码未编译到文件中,需要运行时从可再发行程序包查找载入。这样生成的文件较小,但存在动态载入库的开销。此选项不支持调试。
  • 多线程调试DLL(/MDd),类似于MD,但支持调试,生成的代码性能最低。

为了支持尽可能多样的用户需求,你可能需要为所有4种方式各编译一份二进制文件。

使用调试库

此设置影响你的库是否支持调试。在项目 配置属性高级使用调试库 中设置。此设置必须与运行库匹配:如果支持调试,只能选 MTd 或 MDd;否则只能选 MT 或 MD。

平台工具集

在项目 配置属性常规平台工具集 中设置。如果用户需要安装可再发行程序包,此设置将影响用户需要安装的版本。平台工具集和可再发行程序包版本的对应关系 没有明确的文档列出或不会及时更新 ,因此最好能测试一下。即使不需要安装,用户的项目也必须使用和你一样的平台工具集。

全程序优化

在项目 配置属性高级全程序优化 中设置。如果使用全程序优化,可以将性能优化到极限,但用户必须使用和你的编译器准确一样的版本,包括小版本号。因此一般不会使用此设置。

库体积膨胀的权衡

使用你的库的用户只能使用和你的二进制文件一样的设置 ,因此你可能需要为所有可能的设置各提供一个二进制文件,这样才能最大化允许用户自由设置他自己的项目。当然,这会导致你的库体积膨胀数倍。具体来说,运行库有4种选项,因此你至少要提供4个版本;如果你还需要支持更多平台工具集,还要倍乘。如果你不希望库体积太大,可以思考如下权衡方案:

  • 直接发布源码。此方法的问题主要在于会增加编译时间。
  • 不支持某些配置。此方法可能导致潜在的用户流失,不是所有用户都会愿意为了你的库去修改他全局的编译设置。
  • 设计平凡的C风格二进制接口。C++的非平凡对象跨模块传递(包括函数调用和异常抛出)都可能造成内存管理问题。一种较为复杂的做法是将二进制接口全部设置为平凡的C风格,非平凡的、C++风格的接口在头文件中提供转换功能,这样可以提高单个lib的兼容性,而无需提供多个版本。但是,这种做法将提高设计复杂性,并产生接口转换开销。

TypeScript MSBuild与targets

之前略过了 targets 文件内容的详解。targets 文件不会影响你自己的生成流程,而是会嵌入到用户的 MSBuild 流程中。一般来说,你需要在 targets 文件中处理以下问题:

外部包含目录

用户的 MSBuild 需要知道你的头文件放在哪里,这样用户代码中才能使用简短的相对路径包含你的头文件。在 targets 文件中,你需要在 ClCompile 和 ResourceCompile 项中添加
AdditionalIncludeDirectories,一般需要这样写:

<ItemDefinitionGroup>

<ClCompile>

<AdditionalIncludeDirectories>你的包含目录;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

</ClCompile>

<ResourceCompile>

<AdditionalIncludeDirectories>你的包含目录;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

</ResourceCompile>

</ItemDefinitionGroup>

如果你的库中没有需要编译的资源,也可以不写 ResourceCompile 节点。如果有多个包含目录,用分号分隔。如果你希望使用相对路径,可以使用 $(MSBuildThisFileDirectory) 宏,将会被替换为你的 targets 文件所在的目录。此处指定的目录并不限制必须是你的库内,也可以指定任何绝对路径。例如:

<?xml version="1.0" encoding="utf-8"?>

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>

<MexTools_MatlabExtern>$([System.IO.Directory]::GetDirectories("$(ProgramFiles)MATLAB").GetValue($([MSBuild]::Subtract($([System.IO.Directory]::GetDirectories("$(ProgramFiles)MATLAB").Length), 1))))extern</MexTools_MatlabExtern>

<MexTools_MatlabLib>$(MexTools_MatlabExtern)libwin64microsoft</MexTools_MatlabLib>

<TargetExt>.mexw64</TargetExt>

<MexTools_Configuration>Release</MexTools_Configuration>

<MexTools_Configuration Condition="$(UseDebugLibraries)">Debug</MexTools_Configuration>

</PropertyGroup>

<ItemDefinitionGroup>

<ClCompile>

<AdditionalIncludeDirectories>$(MexTools_MatlabExtern)include;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

<LanguageStandard>stdcpplatest</LanguageStandard>

<SDLCheck>false</SDLCheck>

</ClCompile>

<ResourceCompile>

<AdditionalIncludeDirectories>$(MexTools_MatlabExtern)include;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

<LanguageStandard>stdcpplatest</LanguageStandard>

<SDLCheck>false</SDLCheck>

</ResourceCompile>

</ItemDefinitionGroup>

</Project>

如上例,你可以指定某个不在你库内的已知目录加入外部包含,甚至可以调用一些.NET 函数辅助处理。MSBuild 支持的语法有官方文档详解 ,此处不再赘述。

属性设置与链接方式

你可以在 targets 中为用户的项目配置属性添加额外的条目。例如:图中“链接设置”就不是 VS 内置的配置属性之一,而是来自 NuGet target。这里的链接方式分符号和内联两种,提供这个选项是为了处理一种特殊情形:当你的库是静态库,用户的项目也是静态库时,用户就有两种选项:

  • 符号链接,即在用户库中只有你的库的符号引用,不包含定义实现代码。因此,引用用户库的更下游用户,还必须同时引用你的库。
  • 内联链接,即在用户库中完整包含你的库的所有符号的定义实现。这样,引用用户库的更下游用户无需再专门引用你的库。

如果用户项目不是静态库,一般没有必要使用内联链接,默认符号链接。

要实现这个 UI 功能,你需要准备一个 链接设置.xml :

<?xml version="1.0" encoding="utf-8"?>

<ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework">

<Rule Name="埃博拉酱" PageTemplate="tool" DisplayName="链接设置" SwitchPrefix="/" Order="1">

<Rule.Categories>

<Category Name="cpp" DisplayName="Mex 工具" />

</Rule.Categories>

<Rule.DataSource>

<DataSource Persistence="ProjectFile" ItemType="" />

</Rule.DataSource>

<EnumProperty Name="EbolaChan_MariaDB_LinkMethod" DisplayName="链接方式" Category="cpp" Default="符号">

<EnumValue Name="符号" DisplayName="符号" Description="仅引用本库的符号,不包含定义">

</EnumValue>

<EnumValue Name="内联" DisplayName="内联" Description="将本库的代码全部内联写入输出目标">

</EnumValue>

</EnumProperty>

</Rule>

</ProjectSchemaDefinitions>

Rule 和 EnumProperty 的 Name 需要你起一个在全世界独一无二的名称,由于它们会和用户可能引用的其它 NuGet 库一起加入 MSBuild,需要避免命名冲突问题。所有 DisplayName 则仅供用户识读,简明扼要即可。示例脚本设计了一个配置属性 UI,引导用户设置链接方式。

如果你将要发布的是静态 lib 库,用户的 MSBuild 需要知道那个文件的路径。你需要分别思考你的 lib,以及你的 lib 所符号链接的所有其它 lib,是否存在二进制兼容问题,然后以以下两种不同的方式为用户添加引用:

对于不存在二进制兼容性问题的lib

可以简单写成:

<ItemGroup Condition="$(ConfigurationType)=='StaticLibrary'">

<PropertyPageSchema Include="$(MSBuildThisFileDirectory)链接设置.xml" />

</ItemGroup>

<ItemDefinitionGroup Condition="$(ConfigurationType)!='StaticLibrary'">

<Link>

<AdditionalDependencies>Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>

</Link>

</ItemDefinitionGroup>

<ItemGroup Condition="$(EbolaChan_MariaDB_LinkMethod)=='内联'">

<Library Include="Shlwapi.lib" />

</ItemGroup>

上述代码附加了一个名为 Shlwapi.lib 的静态库。类似于包含目录,这里附加的静态库不必定是你所发布的库,也可以是任何用户机上已知应当存在的库,例如本例 Shlwapi.lib 就是 Windows SDK 的库。

你可以注意到,这里引用了上一步的链接设置,并根据设置的链接方式是符号还是内联,使用了不同的语法来设置引用库:设置 AdditionalDependencies 则将生成符号链接,而设置 Library 将生成内联链接。

如果存在二进制兼容性问题

你还需要根据用户的编译配置路由到正确的 lib 文件,最简单的方法就是直接将编译配置参数文本嵌入文件名。这实则包含两个部分的配置:第一你需要在你自己的 vcxproj 中根据当前编译配置生成相应的文件名,然后你需要在 targets 中根据用户的编译配置将对应文件名添加到 AdditionalDependencies。

在vcxproj中

作为示例,可以在 Project 节点下添加如下内容:

<Target Name="设置程序数据库" BeforeTargets="ClCompile">

<PropertyGroup>

<TargetName>$(ProjectName).%(ClCompile.RuntimeLibrary).$(PlatformToolsetVersion)</TargetName>

<TargetPath>$(OutDir)$(TargetName)$(TargetExt)</TargetPath>

</PropertyGroup>

<ItemGroup>

<ClCompile Update="@(ClCompile)">

<ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>

</ClCompile>

</ItemGroup>

</Target>

<Target Name="设置库输出" BeforeTargets="DoLibOutputFilesMatch">

<ItemGroup>

<Lib Update="@(Lib)">

<OutputFile>$(TargetPath)</OutputFile>

</Lib>

</ItemGroup>

</Target>

这样做是基于 MSBuild 的一个基本运行机制:以 Target 为单元执行任务。执行生成命令时,MSBuild 会收集 vcxproj、targets 以及内置的一系列 XML,按照必定的顺序执行其中定义的 Target。如上例中定义了两个 Target:

一个是“设置程序数据库”,并要求它在 ClCompile 之前执行——ClCompile 是一个内置的 Target 名称。注意之前 targets 文件和此处 ItemGroup 节点中也提到了 ClCompile,但在 ItemGroup 和 ItemDefinitionGroup 节点内定义的是 Item 类型,和作为内置 Target 的 ClCompile 仅仅是名称恰好一样,本质上不是一类东西。内置 ClCompile 作为 Target 被执行时,会生成 IDB 和 PDB,因此必须在该 Target 之前设置正确的输出路径,也就是:

  • 在ItemGroup中,将作为Item的ClCompile的ProgramDataBaseFileName设为 $(OutDir)$(TargetName).pdb ,IDB的路径会自动修改扩展名无需另外设置。 Update=”@(ClCompile)” 意思是更新所有ClCompile项(实际上,vcxproj中会为每个cpp源文件创建一个ClCompile项,所以需要“更新所有”)其中:
  • OutDir就是 配置属性常规输出目录
  • TargetName则在前面PropertyGroup中设为了 $(ProjectName).%(ClCompile.RuntimeLibrary).$(PlatformToolsetVersion) 。其中:
  • ProjectName顾名思义就是整个的项目名称
  • ClCompile.RuntimeLibrary 就是 配置属性C/C++代码生成运行库 。注意它不是全局属性宏,而是ClCompile项的属性,所以引用语法也不一样,前缀是%而不是$。中英文对应关系是:
  • 多线程:MultiThreaded
  • 多线程调试:MultiThreadedDebug
  • 多线程DLL:MultiThreadedDLL
  • 多线程调试DLL:MultiThreadedDebugDLL
  • PlatformToolsetVersion就是 配置属性常规平台工具集
  • 这样一来TargetName就成了三个字段的拼接,例如 MariaDB_Connector.MultiThreaded.145 ,然后用它进一步拼接成TargetPath: $(OutDir)$(TargetName)$(TargetExt)
  • OutDir前已述,TargetExt就是 配置属性高级目标文件扩展名 ,对静态库默认是lib。

下一个 Target“设置库输出”将会用到 TargetPath。该 Target 要求在 DoLibOutputFilesMatch 之前执行——这也是一个内置 Target 名称。在 DoLibOutputFilesMatch 中,MSBuild 将检查 TargetPath、TargetName 和 Lib 项的 OutputFile 属性是否匹配,因此必须在此之前正确设置 Lib 项属性。如注释中所述,在“设置程序数据库”阶段,Lib 项尚未创建——它是在 ClCompile 之后、DoLibOutputFilesMatch 之前创建的(由内置 XML 脚本创建,在 vcxproj 中不可见),因此不能在 ClCompile 之前设置该 Lib 项属性。语法与 ClCompile 类似,将所有 Lib 项的 OutputFile 更新为 TargetPath。

以上设置可以令你的 vcxproj 输出的 lib 文件名始终包含必要的配置信息,主要就是 ClCompile.RuntimeLibrary 和 PlatformToolsetVersion。

在targets中

始终记得,targets 将在用户的而非你的 MSBuild 中执行。作为示例,同样在 Project 节点内添加链接设置:

<ItemGroup Condition="$(ConfigurationType)=='StaticLibrary'">

<PropertyPageSchema Include="$(MSBuildThisFileDirectory)链接设置.xml" />

</ItemGroup>

<Target Name="埃博拉酱_MariaDB_符号链接" BeforeTargets="Link" Condition="$(ConfigurationType)!='StaticLibrary'">

<PropertyGroup>

<RuntimeLibrary>%(ClCompile.RuntimeLibrary)</RuntimeLibrary>

</PropertyGroup>

<ItemGroup>

<Link Update="@(Link)">

<AdditionalDependencies>$(MSBuildThisFileDirectory)MariaDB_Connector.$(RuntimeLibrary).$(PlatformToolsetVersion).lib;%(AdditionalDependencies)</AdditionalDependencies>

</Link>

</ItemGroup>

</Target>

<Target Name="埃博拉酱_MariaDB_内联链接" AfterTargets="FixupCLCompileOptions" Condition="$(EbolaChan_MariaDB_LinkMethod)=='内联'">

<PropertyGroup>

<RuntimeLibrary>%(ClCompile.RuntimeLibrary)</RuntimeLibrary>

</PropertyGroup>

<ItemGroup>

<Library Include="$(MSBuildThisFileDirectory)MariaDB_Connector.$(RuntimeLibrary).$(PlatformToolsetVersion).lib" />

</ItemGroup>

</Target>

对于符号链接的情形,需要确保在 Link Target,也就是执行链接步骤之前设置附加依赖项。这里有一个之前未详述的语法限制,就是在 Update 项属性时,不能直接引用其它项的属性。而实际上,我们恰恰需要在 Link 项属性中引用 %(ClCompile.RuntimeLibrary) ,这无法直接做到,因此需要先在 PropertyGroup 中设置一个全局属性宏,可以直接命名为 RuntimeLibrary。那之后,可以在更新 Link 项的 AdditionalDependencies 时引用它。这个 Target 的名称,将会和用户所有其它的 NuGet 库中可能包含的 Target 名称一起参与 MSBuild。如果有重名,将会导致某些 Target 被替换而失败。所以此处的 Target 名称应该起一个你自己独特的、不太可能和别人重复的名字。你可能还记得在不存在二进制兼容问题的情形中,我们是在 ItemDefinitionGroup 节点中设置的 Link 属性。这里为什么不能这样写呢?这实则也涉及 MSBuild 执行顺序问题。ItemDefinitionGroup 是在任何项创建之前执行的,也就是说定义 ItemDefinitionGroup 时,我们需要引用的 ClCompile 项尚未创建,当然也就无法获取其 RuntimeLibrary 属性。而等到该项属性被设置后,MSBuild 也是不会再回头去重新查看 ItemDefinitionGroup 的。因此这里需要的是在项创建阶段修改 Link 项属性,只能在 Target 内部使用 ItemGroup Update 语法。

对于内联链接的情形,用类似方式设置 Library 即可。

其它项目配置

除了上述两项常见的,你还可以在 targets 中对用户的 MSBuild 流程进行任何自定义配置。更多高级用法请参阅 MSBuild 相关文档教程,本文不胜枚举,仅提供一些启发性示例:

<ItemDefinitionGroup>

<ClCompile>

<AdditionalIncludeDirectories>$(MexTools_MatlabExtern)include;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

<LanguageStandard>stdcpplatest</LanguageStandard>

<SDLCheck>false</SDLCheck>

</ClCompile>

</ItemDefinitionGroup>

本示例将用户的语言标准设置为最新,并且关闭 SDL 检查。是的,一个 NuGet 包是有权限通过 targets 直接修改用户的全局生成配置的。因此理论上,可能存在精心设计的恶意 targets 利用 MSBuild 安全漏洞对用户进行攻击。在使用任何 NuGet 包生成前检查其 targets 有无可疑内容,是一个良好的安全习惯。

发布前的调试

作为一个负责任的库开发者,提议你在发布 NuGet 之前进行充分的调试测试,由于 NuGet 不支持删除已发布的库的任何版本,如果你发布了一个丑陋的充满 bug 的无法使用的版本,它将永远无法从你的发布记录中移除,你只能添加文本说明将其标记为弃用。如果你希望尽可能减少这种不优雅的事故,应当在发布之前确认你的 NuGet 包能够被正确引用并成功生成。

第一你当然需要自己设计一个测试项目,引用库中的任何待测功能。但是,由于该库尚未发布到 NuGet,你当然不能用 NuGet 的机制自动引用该库,因此需要手动编辑 vcxproj 文件,如下示例:

<ImportGroup Label="调试">

<Import Project="$(USERPROFILE)source
eposmariadb-connector-cppNuGetuild
ativeMariaDB_Connector.targets" Condition="'$(Configuration)'=='调试 MariaDB'" />

</ImportGroup>

<ImportGroup Label="ExtensionTargets">

<Import Project="..packagesMariaDB_Connector.1.1.7uild
ativeMariaDB_Connector.targets" Condition="Exists('..packagesMariaDB_Connector.1.1.7uild
ativeMariaDB_Connector.targets') And '$(Configuration)' != '调试 MariaDB'" />

</ImportGroup>

示例中是两个 ImportGroup 节点,分别具有 Label 名曰“调试”和 ExtensionTargets。其中,ExtensionTargets 表明该节点是 Visual Studio 自动创建的,实际上就是 NuGet 自动化流程的结果。如果你的项目中安装了任何 NuGet 包,都会在这个节点中有记录。如果你的项目未引用 NuGet 包,可以忽略这个节点。但如果引用了,特别是如果引用了你将要发布的库的旧版本(而你想要测试新版),你必须在这里将其暂时屏蔽。最优雅的方法就是创建一个专门的调试用 Configuration(在 项目属性配置管理器 中),然后为旧库的 Import 节点新增一个 And '$(Configuration)' != '调试 MariaDB' 附加条件,这样在激活调试配置时,该 Import 就会被屏蔽。

无论如何,你始终需要 Import 新库。如示例中的做法,新建一个 ImportGroup,然后在其中设置新库的 targets 文件的位置,并设置其条件为仅在调试配置中使用。这个路径可以使用 Windows 自带的环境变量,如示例中的 USERPROFILE,也可以设置任何绝对路径。这样一来,你就可以用你的测试项目模拟用户环境,Import 你将要发布的库,尝试生成,测试各项功能是否正常。

注意你新建的 ImportGroup 必须位于 ExtensionTargets 之前,由于 NuGet 安装新库或升级版本时会修改 vcxproj 中的最后一个 ImportGroup;为了避免你的被修改,不能将其放在最后一个。

打包和发布

打包需要下载一个 nuget.exe ,它是一个独立于 Visual Studio 的命令行工具,不会随 VS 被安装,需要手动单独下载。那之后,你需要将你放置该 exe 的位置添加到用户环境变量 Path 以便在命令行中调用。最后,执行 nuget pack 根目录 ,根目录就是你的 nuspec 所在的目录,然后就会在当前工作目录生成一个 nupkg 文件,这就是你将要上传到 NuGet 的包。它实际上是一个 zip 压缩包,你可以用大多数压缩文件管理器打开它,查看其中内容是否正确。一般来说会比你打包的根目录下多出一些额外的元数据文件,这是正常的。另外,如果有非 ASCII 字符可能会被 URL 编码,这一般也无需担心,用户安装后会正常解码使用。

有时你可能会希望将一些额外的、不在根目录下的文件也包进去,例如存储库最外层的 README.md,由于 GitHub 只能显示最外层的 README,要想把它包入 nupkg 中就只能临时拷贝到包根目录下。以下是一个实用的 PowerShell 脚本:

Copy-Item .README.md .NuGet
Copy-Item .include*

nuget pack NuGet

上述脚本假定当前工作目录就是存储库最外层,也是包根目录(假定命名为 NuGet)的直接上级。因此第一将 README 复制到包根目录中,然后将 include 目录内所有项目(包括文件和目录)全部复制到 build
ative 下,强制覆盖且遍历所有目录层级。最后执行打包。当然,这些命令是高度自定义的,你需要根据你存储库的实际布局调整。

最后一步,登录 NuGet 网站 ,将你的 nupkg 上传。如果一切设置正确,你一般不需要进行任何额外配置,一路下一步、提交等等就行了。发布后,你需要等待一段时间才能在 NuGet 库浏览器中找到它——一般不会超过1小时。

下载使用

使用 C++ NuGet 库超级简单,只需要在项目的“管理 NuGet 包”页面搜索安装即可,库开发者精心设计的 targets 文件自动帮你完成包含目录和静态库的导入(注意检查 targets 是否安全!),无需手动设置。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容