NuGet 跨平台插件

在 NuGet 4.8+ 中,已添加对跨平台插件的支持。 这是通过构建新的插件扩展模型实现的,该模型必须符合一组严格的操作规则。 这些插件是自包含可执行文件(可在 .NET Core 环境中运行),可由 NuGet 客户端在单独的进程中启动。 这是真实的一次性写入,可在任何位置运行插件。 它将适用于所有 NuGet 客户端工具。 这些插件可以是 .NET Framework(NuGet.exe、MSBuild.exe 和 Visual Studio)或 .NET Core (dotnet.exe)。 已定义 NuGet 客户端与插件之间的受版本控制的通信协议。 在启动握手期间,这 2 个进程会协商协议版本。

要涵盖所有 NuGet 客户端工具方案,则同时需要 .NET Framework 和 .NET Core 插件。 下面介绍插件的客户端/框架组合。

客户端工具 框架
Visual Studio .NET Framework
dotnet.exe .NET Core
NuGet.exe .NET Framework
MSBuild.exe .NET Framework
Mono 上的 NuGet.exe .NET Framework

工作原理

高级别工作流的描述如下:

  1. NuGet 发现可用插件。
  2. 如果适用,NuGet 将按优先级顺序循环访问插件,并逐一启动插件。
  3. NuGet 将使用第一个可为请求提供服务的插件。
  4. 不再需要使用插件时,系统会将其关闭。

一般插件要求

当前协议版本是 2.0.0。 在此版本下,要求如下所示:

  • 具有有效的、受信任的 Authenticode 签名程序集,将在 Windows 和 Mono 上运行。 没有针对 Linux 和 Mac 上运行的程序集的特殊信任要求。 相关问题
  • 支持在 NuGet 客户端工具的当前安全上下文中进行无状态启动。 例如,NuGet 客户端工具不会在稍后介绍的插件协议之外执行提升或其他初始化。
  • 除非显式指定,否则为非交互式。
  • 遵循协商的插件协议版本。
  • 在合理的时间段内响应所有请求。
  • 优先处理任何进行中的操作的取消请求。

以下说明详细介绍了技术规范:

客户端 - 插件交互

NuGet 客户端工具和插件通过标准流(stdin、stdout、stderr)与 JSON 通信。 所有数据必须采用 UTF-8 编码。 插件通过参数“-Plugin”启动。 如果用户在未使用此参数的情况下直接启动插件可执行文件,则该插件可提供信息性消息而不是等待协议握手。 协议握手超时时间为 5 秒。 插件应在尽可能短的时间内完成设置。 NuGet 客户端工具将通过传入 NuGet 源的服务索引来查询插件支持的操作。 插件可以使用服务索引来检查是否存在受支持的服务类型。

NuGet 客户端工具与插件之间的通信是双向的。 每个请求的超时时间为 5 秒。 如果操作本应花费更长的时间,则相应进程应发送进度消息,以防请求超时。在非活动状态保持 1 分钟后,插件将被视为空闲并关闭。

插件安装和发现

系统将通过基于约定的目录结构来发现插件。 CI/CD 方案和高级用户可使用环境变量来替代行为。 使用环境变量时,只允许使用绝对路径。 请注意,NUGET_NETFX_PLUGIN_PATHSNUGET_NETCORE_PLUGIN_PATHS 仅适用于 5.3+ 及以上版本的 NuGet 工具。

  • NUGET_NETFX_PLUGIN_PATHS - 定义将由基于 .NET Framework 的工具 (NuGet.exe/MSBuild.exe/Visual Studio) 使用的插件。 优先于 NUGET_PLUGIN_PATHS。 (仅限 NuGet 版本 5.3+)
  • NUGET_NETCORE_PLUGIN_PATHS - 定义将由基于 .NET Core 的工具 (dotnet.exe) 使用的插件。 优先于 NUGET_PLUGIN_PATHS。 (仅限 NuGet 版本 5.3+)
  • NUGET_PLUGIN_PATHS - 定义将用于 NuGet 进程的插件,保留优先级。 如果已设置该环境变量,它将替代基于约定的发现。 如果已指定任何一个框架特定的变量,则忽略。
  • 用户位置,%UserProfile%/.nuget/plugins 中的 NuGet 主页位置。 无法替代此位置。 不同的根目录将用于 .NET Core 和 .NET Framework 插件。
框架 根发现位置
.NET Core %UserProfile%/.nuget/plugins/netcore
.NET Framework %UserProfile%/.nuget/plugins/netfx

每个插件都应安装在它自己的文件夹中。 插件入口点将为安装文件夹的名称,其中包含 .NET Core 的 .dll 扩展和 .NET Framework 的 .exe 扩展。

.nuget
    plugins
        netfx
            myPlugin
                myPlugin.exe
                nuget.protocol.dll
                ...
        netcore
            myPlugin
                myPlugin.dll
                nuget.protocol.dll
                ...

注意

目前没有用于安装插件的用户情景。 只需将所需文件移动到预先确定的位置即可。

不支持的操作

新的插件协议支持两个操作。

操作名称 最低协议版本 最低 NuGet 客户端版本
下载包 1.0.0 4.3.0
身份验证 2.0.0 4.8.0

在正确的运行时下运行插件

对于 dotnet.exe 方案中的 NuGet,插件需要能够在 dotnet.exe 的特定运行时下执行。 插件提供程序和使用者需确保使用的是兼容的 dotnet.exe/插件组合。 user-location 插件可能在此情况下出现潜在问题:例如,2.0 运行时下的 dotnet.exe 尝试使用为 2.1 运行时编写的插件。

功能缓存

插件的安全验证和实例化的成本较高。 下载操作的频率远高于身份验证操作,但是普通的 NuGet 用户只可能拥有身份验证插件。 为了改善体验,NuGet 将缓存给定请求的操作声明。 此缓存针对于每个插件,插件密钥作为插件路径,并且此功能缓存的有效期为 30 天。

缓存位于 %LocalAppData%/NuGet/plugins-cache 中,并通过环境变量 NUGET_PLUGINS_CACHE_PATH 进行替代。 要清理此缓存,可以通过 plugins-cache 选项运行 locals 命令。 现在,all locals 选项也将删除插件缓存。

协议消息索引

协议版本 1.0.0 消息

  1. Close

    • 请求方向:NuGet -> 插件
    • 请求不会包含有效负载
    • 不会有响应。 适当的响应是为了使插件进程迅速退出。
  2. 复制包中的文件

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包 ID 和版本
      • 包源存储库位置
      • 目标目录路径
      • 包中要复制到目标目录路径的文件的可枚举项
    • 响应将包含:
      • 指示操作结果的响应代码
      • 目标目录中已复制文件的完整路径的可枚举项(如果操作成功)
  3. 复制包文件 (.nupkg)

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包 ID 和版本
      • 包源存储库位置
      • 目标文件路径
    • 响应将包含:
      • 指示操作结果的响应代码
  4. 获取凭据

    • 请求方向:插件 -> NuGet
    • 请求将包含:
      • 包源存储库位置
      • 使用当前凭据从包源存储库获取的 HTTP 状态代码
    • 响应将包含:
      • 指示操作结果的响应代码
      • 用户名(如果可用)
      • 密码(如果可用)
  5. 获取包中的文件

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包 ID 和版本
      • 包源存储库位置
    • 响应将包含:
      • 指示操作结果的响应代码
      • 包中文件路径的可枚举项(如果操作成功)
  6. 获取操作声明

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包源的服务 index.json
      • 包源存储库位置
    • 响应将包含:
      • 指示操作结果的响应代码
      • 受支持的操作(如下载包)的可枚举项(如果操作成功)。 如果插件不支持包源,则该插件必须返回受支持的操作的空集。

注意

此消息已在版本 2.0.0 中进行了更新。 由客户端保持后向兼容性。

  1. 获取包哈希

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包 ID 和版本
      • 包源存储库位置
      • 哈希算法
    • 响应将包含:
      • 指示操作结果的响应代码
      • 使用请求的哈希算法的包文件哈希(如果操作成功)
  2. 获取包的版本

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包 ID
      • 包源存储库位置
    • 响应将包含:
      • 指示操作结果的响应代码
      • 包版本的可枚举项(如果操作成功)
  3. 获取服务索引

    • 请求方向:插件 -> NuGet
    • 请求将包含:
      • 包源存储库位置
    • 响应将包含:
      • 指示操作结果的响应代码
      • 服务索引(如果操作成功)
  4. 握手

    • 请求方向:NuGet <-> 插件
    • 请求将包含:
      • 当前插件协议版本
      • 支持的最低插件协议版本
    • 响应将包含:
      • 指示操作结果的响应代码
      • 协商的协议版本(如果操作成功)。 失败将导致插件终止。
  5. 初始化

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • NuGet 客户端工具版本
      • NuGet 客户端工具有效语言。 这其中考虑了 ForceEnglishOutput 设置(若已使用)。
      • 默认请求超时,它将取代协议默认值。
    • 响应将包含:
      • 指示操作结果的响应代码。 失败将导致插件终止。
  6. 日志

    • 请求方向:插件 -> NuGet
    • 请求将包含:
      • 请求的日志级别
      • 要记录的消息
    • 响应将包含:
      • 指示操作结果的响应代码。
  7. 监视 NuGet 进程退出

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • NuGet 进程 ID
    • 响应将包含:
      • 指示操作结果的响应代码。
  8. 预提取包

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包 ID 和版本
      • 包源存储库位置
    • 响应将包含:
      • 指示操作结果的响应代码
  9. 设置凭据

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包源存储库位置
      • 最后一个已知的包源用户名(如果可用)
      • 最后一个已知的包源密码(如果可用)
      • 最后一个已知的代理用户名(如果可用)
      • 最后一个已知的代理密码(如果可用)
    • 响应将包含:
      • 指示操作结果的响应代码
  10. 设置日志级别

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 默认日志级别
    • 响应将包含:
      • 指示操作结果的响应代码

协议版本 2.0.0 消息

  1. 获取操作声明
  • 请求方向:NuGet -> 插件

    • 请求将包含:
      • 包源的服务 index.json
      • 包源存储库位置
    • 响应将包含:
      • 指示操作结果的响应代码
      • 受支持的操作的可枚举项(如果操作成功)。 如果插件不支持包源,则该插件必须返回受支持的操作的空集。

    如果服务索引和包源为 NULL,则该插件可以通过身份验证进行应答。

  1. 获取身份验证凭据
  • 请求方向:NuGet -> 插件
  • 请求将包含:
    • Uri
    • isRetry
    • NonInteractive
    • CanShowDialog
  • 响应将包含:
    • 用户名
    • 密码
    • Message
    • 身份验证类型列表
    • MessageResponseCode