在 winget 到来之际,再谈谈 scoop

Microsoft Build 2020 开发者大会在几天前召开了,除了 .NET, C# 等的常规更新之外,微软也发布了全新的、官方支持的命令行安装工具 winget。winget 出现的意义不用多说:Windows 终于迎来了受官方支持的命令行安装工具。此前虽然有 choco, scoop 等,但都只是第三方工具而已,如今微软第一方下场,无疑给开发者们带来了极大的鼓舞。

winget 的使用体验我一直有在 Telegram 频道 里更新,在这里不再多谈——winget 目前还没到 1.0 版本,现在只是 preview 而已,某些点今天谈完可能第二天就给更新了。或许到了 1.0 版本释出时我会再更一篇文章吧。

谈谈 scoop 现有的问题

我在这里更想谈一谈 scoop 这个第三方的 cli installer。我一直一来都是一个 scoop 吹,本博客之前也曾介绍过 scoop1,但经过了这一年多的使用,我也逐渐意识到了许多问题,这些问题或许不是“痛点”,但却总让人有股隔靴搔痒的无力感。

重名问题

当多个 bucket 内有同名 manifest 时,会按照 bucket 的顺序,自动安装最靠前的 manifest。比如我的 backit 与 main bucket 里都存在 ffsend,执行 scoop install ffsend 时,scoop 会安装 backit/ffsend,而非有一个询问。虽然可以用 scoop install main/ffsend 来指定安装,但 scoop 官方是应该注意到这一点的,而非让用户每次都 search 一下。

不受关注的软件

scoop 的 bucket 是靠社区维护的,通常情况下这意味着 bug 更容易被发现——最先发现问题的人通常都会去开 issue 或 pull request。

但当某个软件没那么受人关注,或人们根本没有发现其问题时,就出现另一个问题了:scoop 里的软件版本滞后于官方。

比如 onefetch,在 2019/11/06 的 manifest 更新 2 之后,onefetch 在自己的 release 里发布了 3 个新版本3,而 scoop 里的 manifest 再无更新。虽然这是因为 onefetch 的 release 总是变换命名风格,但 scoop 应该尽力避免这一问题。

人手过少与软件过多

ScoopInstaller 组织里公开显示的有 8 个人4,而足够活跃的仅仅有 @r15ch13@Ash258 两个人5,scoop 并非一个有盈利的项目,这两位也都不是全职维护 scoop,这两人除了自己的本职工作外,能有多少时间投入进来呢?

而仅 main 与 extras 两个 bucket 的软件数量,就已经达到了 1786 个,虽然有社区的力量,但每天 review PR 都够累死的。

main 与 extras 包含的 manifest 数量。2020/05/21 15:14

当一个 bucket 只有 20 个软件时(指我的 backit),维护者完全有精力隔三岔五地看一眼整个的 checkver 的 log,但当一个 bucket 有 200 个时,就完全不一样了。就比如现在的 main bucket 经常会出现更新滞后的情况,毕竟更新脚本跑起来也是需要时间的。(有兴趣的可以比较一下 batkiz/backitScoopInstaller/Main 里 ffsend 的更新时间。)

功能停滞

人手过少带来的另一个问题就是 scoop 本身的功能更新几乎停滞,今年以来在 master 分支上仅仅有两个 commit6,还都是 fix。新功能的添加也是如此,scoop download 命令早在 2017 年就被 issue 提出,而 2019/12/06 的 #3782 PR 为 scoop 添加了此功能,一直未被合并,今年 5 月又被离奇关闭7。让人不得不担心 scoop 项目的发展。

没有明确的 roadmap

scoop 项目至今已经发展了 7 年,却仍没有一个明确的 roadmap,功能的更新也毫无规划。官方的几位开发者除了偶尔对 scoop 进行更新外,每天在忙的就是处理 bucket 里的各种 issue,审批望不到尽头的 PR。

shim 带来的问题

scoop 的一个功能特性就是防止 Path pollution from installing lots of programs8,由于 Windows 上并没有像 *nix 上一样强大的链接,这一点是靠所谓的 shim 实现的。shim 的作用基本就是对 example 生成一个非常小的 example.exe,读取 example.shim 内指定的源 example.exe 路径,(传递命令行参数并)将其启动。

这是一个非常聪明的做法,但有时候却也会带来问题。如在 Windows terminal 里,如果用 pwsh shim 作为启动的 shell,会导致对接收到的 Ctrl + c 的处理不正常:我们想要的是将当前正在运行的命令强行停止,pwsh shim 却会直接将 pwsh 停止。9虽然我们可以通过指定 pwsh 原路径的方法避免,但这又有违了 scoop 进行 shim 的初衷。

依赖问题

有些读者可能看到这里会有些迷惑,解决依赖问题怎么会成为一个问题呢?*nix 上的包管理器不都是在吹自己能处理好依赖吗?

在通常情况下有且仅有一个 package manager 的 *nix 系统之上,包管理器帮助解决依赖问题会很棒。

但在 Windows 系统上,一切都变了:几乎所有软件都没有依赖,最多是 msvc runtime/dotnet runtime;同时用户安装软件的来源也是五花八门的:从官网下载的 exe 文件、MS Store、scoop、choco 等“包管理器”等等等等。

这种情况下,当 scoop 试图接管依赖处理时,就非常令人不爽了。由于 scoop 是按 package 来设定的依赖,而非在环境变量中搜寻是否存在需要的 exe 文件。当你通过其他方式安装了 A,又想要通过 scoop 安装依赖于 A 的 B 时,scoop 只会再给你安装一份 A,非常之 unintelligible。10

这样的依赖处理方式还带来了另外一个问题:卸载软件时,无法顺带卸载其不会再用到的依赖。scoop 并没有区分 lib 与 exe,这样也就无法确定你的软件是否能被安全地移除。最终只能是用户在无意间 scoop list 时,发现一个自己毫无印象的软件,然后将其卸载。

我们真的如同我们想象的那般需要 portable 吗

scoop 在普通情况下非常之 portable,理论上 $env:SCOOP 下的文件可以在不同电脑间相互移动,所需要的仅仅是设定好 scoop 所需要的几个环境变量——但是等等,我们真的需要这一点吗?

scoop 的 nonportable bucket 与种种以 -np 结尾的 manifest 已经证实了 portable 不是灵丹妙药,是不能解决所有的软件的:比如 Windows terminal 等以 appx 为打包格式的软件,根本无法指定安装地址;yarn-np 等软件如果进行极端的 portable 化会导致每次更新都丢数据。

不可否认,这个世界上有很多人同时使用多台电脑,或者想要完全的 portable 软件。但对我们这些仅仅拥有一台电脑、并不太在意软件是否是 portable 的、甚至根本不在乎环境变量是不是被污染的人呢?为了 portable, path clean 而付出的额外的代价,我们是否也应该承受呢?仅就我而言,需要的可能只是一个恰倒好处的 CLI installer 罢了。


设计一个 CLI installer

scoop 无疑是一个值得使用的 package manager,上面列出了的种种缺点也都是站在我个人立场上的,毕竟吾之蜜糖,彼之砒霜,适合自己的才是最好的。

但如果让我来设计的话,一个完全只符合我 个人需求 的 CLI installer 会是怎么样的呢?

  • 安装过程通过 pwsh 脚本定义,增强自由度。
  • 对 exe, msi, appx 等用 inno, Add-AppxPackage 等安装,参考 winget。
  • 对 zip 进行解压等,将 manifest 中定义的 bin 写入环境变量。
  • 卸载过程则参考 winget 后续的动作。
  • dependencies 则是在 manifest 中写入所需的 exe,安装过程中搜寻整个环境变量,如果不存在则推荐安装,并不强制。
  • 利用 GitHub API,manifest 的自动更新出问题时自动新建 issue。
  • 数据持久化仅在 update 时进行,通过 manifest 中定义的文件/文件夹或脚本,来在不同版本文件夹间互相复制等。

当然,这只是目前的构想,后续的开发过程中一定会有所更改。

那么问题来了,我什么时候能用上这个 CLI installer 呢?

下周发布 PPT,今年 12 月第一阶段开源!(逃