NuGet 跨平台插件

添加了 NuGet 4.8+ 对跨平台插件的支持。 这是通过构建新的插件扩展性模型来实现的,该模型必须符合一组严格的作规则。 插件是自包含可执行文件(.NET Core 世界中的可运行对象),NuGet 客户端在单独的进程中启动。 这是一个真正的“编写一次,到处运行”的插件。 它将使用所有 NuGet 客户端工具。 插件可以采用任何编程语言编写,但最简单的插件开发和安装体验是使用 .NET 编写的。 NuGet 客户端和插件之间的版本控制通信协议已定义。 在启动握手期间,2 个进程协商协议版本。

工作原理

高级工作流可按如下所述进行描述:

  1. NuGet 发现可用的插件。
  2. 如果适用,NuGet 将按优先级顺序循环访问插件,并逐个启动它们。
  3. NuGet 将使用可处理请求的第一个插件。
  4. 不再需要插件时,这些插件将被关闭。

常规插件要求

当前协议版本为 2.0.0。 在此版本中,要求如下:

  • 在当前 NuGet 客户端工具的安全上下文中支持无状态启动。 例如,NuGet 客户端工具不会在稍后所述的插件协议之外执行提升或其他初始化。
  • 非交互式,除非显式指定。
  • 遵守商定的插件协议版本。
  • 在合理的时间段内响应所有请求。
  • 执行任何正在进行的操作的取消请求。

从 PATH 环境变量(例如通过 dotnet tool安装)发现的插件还必须与文件名模式 nuget-plugin-*匹配。 该 nuget-plugin- 部分必须完全以小写字母编写。

NuGet 6.12(MSBuild 17.12 和 .NET SDK 9.0.100)及更早版本在 Windows 上还要求插件用 Authenticode 签名。

以下所列的规范更详细地介绍了技术规格:

客户端 - 插件交互

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

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

插件安装和发现

NuGet 从基于约定的目录结构搜索插件,并扫描 PATH 环境变量。

基于约定的发现

CI/CD 场景和高级用户可以使用环境变量来覆盖行为。 使用环境变量时,只允许绝对路径。 请注意,仅在 NuGet 工具 5.3+ 及更高版本中才可使用 NUGET_NETFX_PLUGIN_PATHSNUGET_NETCORE_PLUGIN_PATHS

  • 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 进程的插件,保留优先级。 如果设置了此环境变量,它将替代基于约定的发现。 如果指定了任何一个特定于框架的变量,则忽略该变量。
  • 用户位置,NuGet 主页位置。%UserProfile%/.nuget/plugins 无法重写此位置。 其他根目录将用于 .NET Core 和 .NET Framework 插件。
框架 根发现位置 使用者
.NET 核心 %UserProfile%/.nuget/plugins/netcore dotnet CLI(命令行界面)
.NET 框架 %UserProfile%/.nuget/plugins/netfx MSBuild、NuGet.exe、Visual Studio

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

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

路径发现

NuGet 6.13 开始,NuGet 将在 PATH 环境变量中提供的每个目录搜索与模式 nuget-plugin-*匹配的文件。 模式匹配区分大小写, nuget-plugin- 必须完全以小写字母编写。 在 Windows 上,文件必须具有 .exe.bat 扩展名。 在 Linux 和 Mac 上,文件必须设置可执行位。

这样,NuGet 插件就可以通过 dotnet tool 命令、WinGet、Linux 分发包管理器或任何其他方法安装,这些方法可将可执行文件置于用户的 PATH 上。 这也允许使用任何编程语言编写 NuGet 插件(以前,Linux 和 Mac 的插件必须用 .NET 编写)。

我们建议在 .NET 中开发插件,以便可以使用 NuGet.Protocol 包 来避免编写 json RPC 代码,并允许客户通过它 dotnet package search nuget-plugin发现插件。

支持的操作

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

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

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

对于 dotnet.exe 方案中的 NuGet,插件需要能够在 dotnet.exe的特定运行时下执行。 责任在于插件供应商和用户,以确保使用兼容的 dotnet.exe/plugin 组合。 例如,在 2.0 运行时下 dotnet.exe 尝试使用为 2.1 运行时编写的插件时,用户位置插件可能会出现潜在问题。

能力缓存

插件的安全验证和实例化成本高昂。 下载操作的频率比身份验证操作更频繁,但是平均而言,NuGet 用户通常只有一个身份验证插件。 为了提升用户体验,NuGet将缓存特定请求的操作声明。 此缓存是每个插件,插件密钥是插件路径,此功能缓存的过期时间为 30 天。

缓存位于 %LocalAppData%/NuGet/plugins-cache,并可通过环境变量 NUGET_PLUGINS_CACHE_PATH 覆盖。 若要清除此 缓存,可以使用此选项 plugins-cache 运行局部变量命令。 本地 all 选项现在还将删除插件缓存。

协议消息索引

协议版本 1.0.0 消息:

  1. 关闭

    • 请求方向:NuGet -> 插件
    • 请求将不包含有效负载
    • 不期望有回应。 正确的响应是让插件进程及时退出。
  2. 在包中复制文件

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

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

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

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包标识和版本
      • 软件包来源代码库位置
    • 响应将包含:
      • 指示操作结果的响应代码
      • 在操作成功的情况下,包中可以获得一系列文件路径。
  6. 获取操作声明

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

注释

此消息已在 版本 2.0.0 中更新。 它位于客户端上以保持向后兼容性。

  1. 获取包哈希

    • 请求方向:NuGet -> 插件
    • 请求将包含:
      • 包标识和版本
      • 软件包源存储库位置
      • 哈希算法
    • 响应将包含:
      • 指示操作结果的响应代码
      • 如果操作成功,则使用请求的哈希算法生成包文件的哈希值。
  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
    • 非交互
    • 可以显示对话框
  • 响应将包含
    • 用户名
    • 密码
    • 消息
    • 身份验证类型列表
    • 信息响应代码